diff --git a/.dockerignore b/.dockerignore index 32efc47a4..7d4e773ea 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,35 +1,3 @@ -target/ -images/ -README.md -CONTRIBUTING.md -CHANGELOG.md -.gitignore -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### 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/ - -### VS Code ### -.vscode/ +** +!pom.xml +!src/main/** diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2fcf1c6d0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..bdd19a058 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +--- +blank_issues_enabled: false +contact_links: + - name: Ask a question or get support + url: https://github.com/International-Data-Spaces-Association/DataspaceConnector/discussions + about: Ask a question or request support for using the Dataspace Connector + - name: Take a look at the wiki + url: https://github.com/International-Data-Spaces-Association/DataspaceConnector/wiki + about: Browse the wiki for help regarding the Dataspace Connector diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000..0ac386cb5 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + open-pull-requests-limit: 10 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..c603e8add --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,37 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + pull_request: + branches: [ master, develop ] + +concurrency: + group: ${{ github.head_ref }} + cancel-in-progress: true + +jobs: + build: + timeout-minutes: 30 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + java: [11, 12, 13, 14, 15] + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Cache maven packages + uses: actions/cache@v2 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build with Maven + run: mvn -B -U verify --file pom.xml diff --git a/.gitignore b/.gitignore index e4708e44d..88da88ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ target/ !**/src/main/** !**/src/test/** +/log/ + ### STS ### .apt_generated .classpath diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index e76d1f324..648bafa3e 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; + import java.util.Properties; public class MavenWrapperDownloader { diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5ef1e5c..0a82d54ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,179 @@ # Changelog All notable changes to this project will be documented in this file. +## [5.0.0] - 2021-05-17 + +### Added +- Partially support of HATEOAS. +- Add pagination for REST calls on resources. +- Integration and configuration of Jaeger for using open telemetry. +- Set default application name to `Dataspace Connector` in `application.properties`. +- Add custom spring banner. +- Add separate controller methods for each IDS message type. +- Add global exception handlers for `ResourceNotFoundException`, `JsonProcessingException`, and any + `RuntimeException`. +- Add possibility to disable http tracer in `application.properties`. +- Add possibility to restrict depth of returned IDS information on `DescriptionRequest`. + * Change IDS self-description to returning only a list of catalogs instead of their whole content. + * Add possibility to send `DescriptionRequestMessages` for other elements than resources. +- Add remote IDs to each object for tracking origin. +- Support multiple policy patterns in one contract. +- Add Unit tests and integration tests. +- Add quality checks and project reports to `pom.xml`: execute with `mvn verify site`. +- Improve contract negotiation and usage control. + * Add contract agreement validation in `ContractAgreementHandler`. + * Note pre-defined providers for contract offers in `ContractRequestHandler`. + * Use contract agreements for policy enforcement. + * Handle out contract agreements for multiple artifacts (targets) within one negotiation sequence. + * Restrict agreement processing to confirmed agreements. + * Add relation between artifacts and agreements. + +### Changed +- Support of IDS Infomodel v4.0.4 (direct import in `pom.xml`). +- Change IDS Framework version to v4.0.7. +- Http tracer is limited to 10000 characters per log line. +- Log file creation is disabled by default. +- Move Swagger UI to `/api/docs`. +- Change response type from string to object. +- Use correct response codes as defined by RFC 7231. +- Replace old data model: catalogs, resources, representations, artifacts, contract, rules, and + agreements. + * Separate `ResourceRepresentation` into `Representation` and `Artifact`. + * Separate `ResourceContract` into `Contract` and `Rule`. + * Handle data in own database entity. + * Separate management of resources and its relations. + * Define clear interfaces between data model and the IDS Infomodel objects. + * Add IDS object builder classes. + * Build ids:Resource only if at least 1 representation and 1 contract is present. + * Build ids:Representation only if at least 1 artifact is present. + * Build ids:ContractOffer only if at least 1 rule is present. + * Move remote information from `BackendSource` to `Artifact`. +- Strict implementation of model view controller pattern for data management. + * Controller methods for resources and representations. + * Provide strict access control to backend. Information can only be read and changed by services. + * Strict state validation for entities via factory classes. +- Change IDS messaging sequence: Start with `ContractRequestMessage` for automated + `DescriptionRequestMessage` and `ArtifactRequestMessage`. +- Improve data transfer. + * Process bytes instead of strings. + * Remove limit for data in internal database. + * Establish connection via `ArtifactRequestMessage` for always pulling recent data. + +### Fixed +- Fix of buffer overflow in http tracer. +- Make message handler stateless. + +### Security +- Prevent leaking of technology stack in case of errors/exceptions. +- Logger sanitizes inputs to prevent CRLF injections. +- Mass Bindings. +- Timezone independence. + +## [4.3.1] - 2021-04-15 + +### Changed +- Set builder image to JDK 11. + +## [4.3.0] - 2021-03-24 + +### Added +- Configure timeout values for http connections via `application.properties`. + +## [4.2.0] - 2021-03-09 + +### Added +- New policy pattern: connector-restricted data usage. +- Validate `CONNECTOR_RESTRICTED_USAGE` on data request (as a provider). + +## [4.1.0] - 2021-03-02 + +### Added +- Handle `ResourceUpdateMessage`: Update the local copy of resource upon receiving a `ResourceUpdateMessage`. +- Add attribute for endpoint documentation reference to `ResourceMetadata`. +- Store `ownerURI`, `contractID`, `artifactID`, and `originalUUID` in `RequestedResource`. +- Add support for query params, path variables, and additional headers when requesting artifacts. +- Add input validation for query params, path variables, and headers. +- Add usage control framework checking to the classes `PolicyEnforcement` and `PolicyHandler`. +- Add example files for deployment in Kubernetes. + +### Changed +- Configure Spring to fail on unknown properties in request bodies. +- Move settings for policy negotiation and allowing unsupported patterns to `application.properties`. +- Refactor HttpUtils to use the IDS Framework's `HttpService`. +- Add data string as request body instead of request parameter. + +### Fixed +- Exclusive use of the `ConfigurationContainer` for processing the connector's self-description and + configurations to avoid state errors (relevant for the broker communication). + +## [4.0.2] - 2021-02-04 + +### Added +- Add message handler for `ContractAgreementMessage`. + +### Changed +- Answer with a `MessageProcessedNotificationMessage` to the consumer's `ContractAgreementMessage`. +- Save the `ContractAgreement` to the database and the Clearing House when the second +`AgreementMessage` has been processed. +- Refine exception handling in the message building and sending process. +- Update from IDS Framework v4.0.2 to v4.0.3. + +### Fixed +- Send `ContractAgreementMessage` as request message. + +## [4.0.1] - 2021-01-28 + +### Changed +- Update from IDS Framework v4.0.1 to v4.0.2. + +## [4.0.0] - 2021-01-25 + +### Added +- Add public endpoint for self-description without resource catalog and public key. +- Add example endpoints. +- Add exceptions and detailed exception handling. +- Create `UUIDUtils` for uuid handling. +- Create `ControllerUtils` for http responses. +- Add endpoints for contract negotiation. +- Add http tracing and improved logging. +- Add custom profiles for Maven. +- Add negotiation service. +- Add Spring actuators. +- Add contract agreement repository. + +### Changed +- Change object handling and model classes. + - Move attribute `system` from `BackendSource` as `name` to `ResourceRepresentation`. + - Move attribute `sourceType` from `ResourceRepresentation` as `type` to `BackendSource`. + - Migrate `ResourceRepresentation` to map. +- Remove requested resource list from description response. +- Rename broker communication and self-description endpoints. +- Improve exception handling. +- Improve message handler and sending request messages in `de.fraunhofer.isst.dataspaceconnector.services.messages`. +- Change package structure. +- Add abstract class to resource service implementations. +- Edit policy handler. +- Improve `pom.xml`. +- Remove local caching of ids resources. +- Update to IDS Framework v4.0.1. +- Restructure `README.md` and wiki. +- Move code of conduct from `CONTRIBUTING.md` to `CODE_OF_CONDUCT.md`. +- Add response code annotations to endpoint methods. +- Change http response formatting. +- Replace Log4j1 with Log4j2. + +### Fixed +- Update connector of configuration container before sending a broker message. +- Enforce access counter usage by moving it to an isolated method. + ## [3.2.1] - 2020-11-05 -### Changed +### Changed - Update to IDS framework v3.2.3. - Move self-service and example endpoints to admin API. - Improve Dockerfile. - Add key- and truststore to example configuration. -- Add default policy (provide access) to resource on creation. +- Add default policy (provide access) to resource on creation. ### Added - Update and delete resources from broker. @@ -36,13 +201,13 @@ All notable changes to this project will be documented in this file. - Add file URI scheme to paths of KeyStore and TrustStore in config.json. - Add test classes: SelfDescriptionTest, RequestDescriptionTest, RequestArtifactTest, DescriptionRequestMessageHandlingTest, ArtifactRequestMessageHandlingTest. -### Removed +### Removed - IDS Connector certificate file. ## [3.1.0] - 2020-09-29 ### Changed -- Integrate IDS policy language. +- Integrate IDS policy language. - Modify policy patterns. - Adapt policy reader to new policy language. - Adapt usage control implementation to new patterns. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..4e5e30473 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,71 @@ +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), +version 1.4, available at http://contributor-covenant.org/version/1/4. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 425ae327a..792f87af4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,29 +1,48 @@ # Contributing to the Dataspace Connector -The following is a set of guidelines for contributing to The Dataspace Connector. This is an ongoing project of the [Data Economy](https://www.isst.fraunhofer.de/en/business-units/data-economy.html) business unit of the [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) hosted on [GitHub](https://github.com/FraunhoferISST/Dataspace-Connector). You are very welcome to contribute to this project when you find a bug, want to suggest an improvement, or have an idea for a useful feature. For this, always create an issue and a corresponding pull request, and follow our style guides as described below. +The following is a set of guidelines for contributing to The Dataspace Connector. This is an ongoing +project of the [Data Economy](https://www.isst.fraunhofer.de/en/business-units/data-economy.html) +business unit of the [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) hosted on +[GitHub](https://github.com/FraunhoferISST/Dataspace-Connector). You are very welcome to contribute +to this project when you find a bug, want to suggest an improvement, or have an idea for a useful +feature. For this, always create an issue and a corresponding branch, and follow our style +guides as described below. -Please note that we have a [code of conduct](#code-of-conduct) that all developers should stick to. +Please note that we have a [code of conduct](CODE_OF_CONDUCT.md) that all developers should stick to. ## Changelog -We document changes in the [CHANGELOG.md](CHANGELOG.md) on root level which is formatted and maintained according to the rules documented on http://keepachangelog.com. +We document changes in the [CHANGELOG.md](CHANGELOG.md) on root level which is formatted and +maintained according to the rules documented on http://keepachangelog.com. ## Issues -You always have to create an issue if you want to integrate a bugfix, improvement, or feature. Briefly and clearly describe the purpose of your contribution in the corresponding issue. The pre-defined [labels](#labels) improve the understanding of your intentions and help to follow the scope of your changes. +You always have to create an issue if you want to integrate a bugfix, improvement, or feature. +Briefly and clearly describe the purpose of your contribution in the corresponding issue. +The pre-defined [labels](#labels) improve the understanding of your intentions and help to follow +the scope of your changes. -**Bug Report**: As mentioned above, bug reports should be submitted as an issue. To give others the chance to reproduce the error in order to find a solution as quickly as possible, the report should at least include the following information: +**Bug Report**: As mentioned above, bug reports should be submitted as an issue. To give others +the chance to reproduce the error in order to find a solution as quickly as possible, the report +should at least include the following information: * Description: What did you expect and what happened instead? * Steps to reproduce (system specs included) * Relevant logs and/or media (optional): e.g. an image ## Labels -The labels are also listed at the menu item `Issues`. There are two types of labels: one describes the content of the issue and should be used by the developer that creates the issue. The other one, starting with `status`, will be added from the developer that takes on the issue. New issues should be initially marked with `status:open`. +The [labels](https://github.com/FraunhoferISST/DataspaceConnector/labels) are listed at the +[issues](https://github.com/FraunhoferISST/DataspaceConnector/issues). +There are three types of labels: one describes the content of the issue and should be used by the +developer that creates the issue. The other one, starting with `status`, will be added from the +developer that takes on the issue. New issues should be initially marked with `status:open`. +Furthermore, the issues `core-functionality` and `ids-functionality` help to specify the scope of +the issue. They map the structure of the roadmap. * Basic labels: `bug`, `enhancement`, `suggestion`, `documentation` `outdated`, `question`, `discussion` * `status:closed`: issue is closed (after successful approval by issuer and QA) * `status:duplicate`: issue is a duplicate of another linked issue and therefore discontinued * `status:in-progress`: issue has been assigned and is currently being worked on +* `status:on-hold`: issue may be implemented at a later date * `status:open`: issue has been submitted or re-opened recently * `status:out-of-scope`: issue is considered out of the project's scope and therefore not further considered * `status:resolved`: issue has been implemented and tested by a developer @@ -31,93 +50,37 @@ The labels are also listed at the menu item `Issues`. There are two types of lab ## Branches -After creating an issue yourself or if you want to address an existing issue, you have to create a branch with a unique number and name that assigns it to an issue. Therefore, follow the guidelines at https://deepsource.io/blog/git-branch-naming-conventions/. After your changes, update the README.md and CHANGELOG.md with details of changes. Then, create a pull request and note that **committing to the master is not allowed**. Please use the feature `Linked issues` to link issues and pull requests. +This repository has a `dev` branch in addition to the `master` branch. The idea is to always +merge other branches into the `dev` branch (as SNAPSHOT version) and to push the changes from +there into the `master` only for releases. This way, the `dev` branch is always up to date, +with the risk of small issues, while the `master` only contains official releases. + +After creating an issue yourself or if you want to address an existing issue, you have to create a +branch with a unique number and name that assigns it to an issue. Therefore, follow the guidelines +at https://deepsource.io/blog/git-branch-naming-conventions/. After your changes, update the +`README.md`, Wiki, and `CHANGELOG.md` with necessary details. Then, create a pull request and note +that **committing to the master is not allowed**. Please use the feature `linked issues` to link +issues and pull requests. ## Commits -We encourage all contributors to stick to the commit convention following the specification on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). In general, use the imperative in the present tense. A quick overview of the schema: +We encourage all contributors to stick to the commit convention following the specification on +[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). In general, use the +imperative in the present tense. A quick overview of the schema: ``` [optional scope]: [optional body] [optional footer(s)] ``` -Types: `fix`, `feat`, `chore`, `test`, `refactor`, `docs`, `release`. Append `!` for breaking changes to a type. +Types: `fix`, `feat`, `chore`, `test`, `refactor`, `docs`, `release`. Append `!` for breaking +changes to a type. An example of a very good commit might look like this: `feat![login]: add awesome breaking feature` -**Pay attention to never push your IDS keystore or certificate to the repository - not in a single commit! Therefore, the `resources/conf` directory is added to the `.gitignore`.** +**Pay attention to never push your IDS keystore or certificate to the repository - not in a single +commit! Therefore, the `resources/conf` directory is added to the `.gitignore`.** ## Versioning -The Dataspace Connector uses the [SemVer](https://semver.org/) for versioning. The release versions are tagged with their respective version. - -## Code of Conduct - -### Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -### Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -### Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -### Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -### Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -### Attribution - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.4, available at http://contributor-covenant.org/version/1/4. +The Dataspace Connector uses the [SemVer](https://semver.org/) for versioning. The release versions +are tagged with their respective version. diff --git a/Dockerfile b/Dockerfile index 825f474b0..0830af4b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,37 @@ -FROM maven:latest AS maven -LABEL maintainer="Julia Pampus " - -COPY pom.xml /tmp/ - -WORKDIR tmp -RUN mvn verify clean --fail-never - -COPY src /tmp/src/ - -RUN mvn clean package -DskipTests -Dmaven.javadoc.skip=true - -FROM adoptopenjdk/openjdk11:jre-11.0.8_10-alpine -RUN mkdir /app - -COPY --from=maven /tmp/target/*.jar /app/app.jar - -WORKDIR /app/ - +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Dependencies +FROM maven:3-jdk-11 AS maven +WORKDIR /app +COPY pom.xml . +RUN mvn -e -B dependency:resolve + +# Plugins +RUN mvn -e -B dependency:resolve-plugins + +# Classes +COPY src/main/java ./src/main/java +COPY src/main/resources ./src/main/resources +RUN mvn -e -B clean package -DskipTests -Dmaven.javadoc.skip=true + +# Copy the jar and build image +FROM gcr.io/distroless/java-debian10:11 +COPY --from=maven /app/target/*.jar /app/app.jar +WORKDIR /app +EXPOSE 8080 +USER nonroot ENTRYPOINT ["java","-jar","app.jar"] diff --git a/LICENSE b/LICENSE index 92a8a6091..44fff7ea5 100644 --- a/LICENSE +++ b/LICENSE @@ -175,18 +175,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2020 Fraunhofer ISST + Copyright 2020 Fraunhofer-Institut für Software- und Systemtechnik Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 8f3547ef7..940fafe99 100644 --- a/README.md +++ b/README.md @@ -1,269 +1,75 @@ -# Dataspace Connector - -**Contact**: [info@dataspace-connector.de](mailto:info@dataspace-connector.de) -| **Issues**: Feel free to report issues [here](https://github.com/FraunhoferISST/DataspaceConnector/issues) or write an [email](mailto:info@dataspace-connector.de). - -This is an IDS Connector using the specifications of the [IDS Information Model](https://github.com/International-Data-Spaces-Association/InformationModel) with integration of the [IDS Framework](https://gitlab.cc-asp.fraunhofer.de/fhg-isst-ids/ids-framework) for connector configuration and message handling. -It provides a REST API for loading, updating, and deleting simple data resources with data and its metadata, persisted in a local H2 database. Next to the internal database, external HTTP REST endpoints as data sources can be connected as well. -The connector supports IDS conform message handling with other IDS connectors and IDS brokers and implements usage control for eight IDS usage policy patterns. - -**This repository has a `develop` branch in addition to the `master` branch. The idea is to always merge other branches into the `develop` branch (as SNAPSHOT version) and to push the changes from there into the `master` only for releases. This way, the `develop` branch is always up to date, with the risk of small issues, while the `master` only contains official releases.** - -Basic information about the International Data Spaces reference architecture model can be found [here](https://www.internationaldataspaces.org/wp-content/uploads/2019/03/IDS-Reference-Architecture-Model-3.0.pdf). - -**This is an ongoing project of the [Data Economy](https://www.isst.fraunhofer.de/en/business-units/data-economy.html) business unit of the [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html). You are very welcome to contribute to this project when you find a bug, want to suggest an improvement, or have an idea for a useful feature. Please find a set of guidelines at the [CONTRIBUTING.md](CONTRIBUTING.md).** - -**This repository has a `develop` branch in addition to the `master` branch. The idea is to always merge other branches into the `develop` branch (as SNAPSHOT version) and to push the changes from there into the `master` only for releases. This way, the `develop` branch is always up to date, with the risk of small issues, while the `master` only contains official releases.** - -## Content - -- [Features](#features) - - [Technologies](#technologies) - - [IDS Components](#ids-components) -- [Getting started](#getting-started) - - [Java Setup](#java-setup) - - [Docker Setup](#docker-setup) -- [Example Setup](#example-setup) -- [Development](#development) - - [Configurations](#configurations) - - [Proxy](#proxy) - - [Authentication](#authentication) - - [Database](#database) - - [Deployment](#deployment) - - [Maven Build](#maven-build) - - [Docker Setup](#docker-setup) - - [Run Tests](#run-tests) - - [Backend API](#backend-api) -- [License](#license) - -## Features - -This is a list of currently implemented features, which is continuously updated. - -* Settings for TLS, proxy and Spring Boot basic authentication for backend endpoints -* Use valid IDS certificate and request DAT from DAPS -* Data resource registration (CRUD metadata) with internal H2 database -* Backend data handling internal (CRUD data) with internal H2 database -* Backend data handling external with example Rest Api (external spring boot application with H2 database) -* IDS message handling with other IDS connectors (as data provider and data consumer): description request/response, artifact request/response, rejection message -* Read IDS response messages: save requested data & metadata in internal database -* IDS message handling with the IDS broker (IDS lab): available/update, unavailable, query -* Usage control with ODRL policies following the IDS policy language specifications -* Possibility to add multiple representations (different backend connections) to a resource - -### Technologies - -`Java`, `Maven`, `Spring Boot`, `Rest`, `OpenAPI`, `Swagger`, `SLF4J`, `Docker`, `JSON(-LD)` - -### IDS Components - -| Library/Component | Version | License | Owner | Contact | -| ------ | ------ | ------ | ------ | ------ | -| [IDS Information Model Library](https://maven.iais.fraunhofer.de/artifactory/eis-ids-public/de/fraunhofer/iais/eis/ids/infomodel/) | 4.0.0 | Apache 2.0 | Fraunhofer IAIS | [Sebastian Bader](mailto:sebastian.bader@iais.fraunhofer.de) | -| [IDS Information Model Serializer Library](https://maven.iais.fraunhofer.de/artifactory/eis-ids-public/de/fraunhofer/iais/eis/ids/infomodel-serializer/) | 4.0.0 | Apache 2.0 | Fraunhofer IAIS | [Sebastian Bader](mailto:sebastian.bader@iais.fraunhofer.de) | -| [IDS Framework](https://gitlab.cc-asp.fraunhofer.de/fhg-isst-ids/ids-framework) | 3.2.3 | Apache 2.0 | Fraunhofer ISST | [Steffen Biehs](mailto:steffen.biehs@isst.fraunhofer.de) | -| [IDS Broker](https://broker.ids.isst.fraunhofer.de/) | 4.0.0 | not open source | Fraunhofer IAIS | [Sebastian Bader](mailto:sebastian.bader@iais.fraunhofer.de) | -| [DAPS](https://daps.aisec.fraunhofer.de/) | 2.0 | not open source | Fraunhofer AISEC | [Gerd Brost](mailto:gerd.brost@aisec.fraunhofer.de) | - - -## Getting started - -At first, clone the repository: `git clone https://github.com/FraunhoferISST/DataspaceConnector.git`. - -If you want to deploy the connector yourself, follow the instructions of the [Development Section](#development). If you do not want to build the connector yourself and just want to see how two connectors communicate, take a look at the **two test setups placed at the corresponding [release](https://github.com/FraunhoferISST/DataspaceConnector/releases)**. -Both test setups provide a connector as a data provider and one as a data consumer. - -### Java Setup - -Extract the provided `java-setup.zip` file. Make sure you have Java 11 installed and both `.jar` files inside their own folder. -The data provider will be running at https://localhost:8080 and the consumer at https://localhost:8081. - -For requesting data from the provider, open the Swagger UI of the consumer (https://localhost:8081/admin/api with `admin` + `password`) and send a request as shown below. -Due to the missing requested resource, the self-description of the provider is returned in response. To request a specific resource, it has to be created in the provider first. -A more detailed explanation can be found at [Hands-on IDS Communication](https://github.com/FraunhoferISST/Dataspace-Connector/wiki/Hands-on-IDS-Communication). - -![Data Request from Consumer to Provider](images/example.PNG) - -### Docker Setup - -Extract the provided `docker-setup.zip` file. Make sure you have Docker Compose installed and run `docker-compose build --no-cache` and then `docker-compose up` inside the extracted folder. -In doing so, the provided `.jar` files will be built up as Docker Images and started as a data provider running at http://localhost:8080/ and a data consumer running at http://localhost:8081/. - -For requesting data from the provider, please remind that all applications are running inside isolated docker containers. So don't request e.g. http://localhost:8080/api/ids/data but http://provider:8080/api/ids/data. - - -## Example Setup - -An instance of the Dataspace Connector v2.0 is currently available in the IDS Lab at https://simpleconnector.ids.isst.fraunhofer.de/. -It can only be reached from inside a VPN network. To get your IP address unblocked, please contact [Julia Pampus](mailto:julia.pampus@isst.fraunhofer.de). -* The connector self-description is available at https://simpleconnector.ids.isst.fraunhofer.de/ (GET). -* The **open endpoint for IDS communication** is https://simpleconnector.ids.isst.fraunhofer.de/api/ids/data (POST). -* The backend API (available at `/admin/api`) and its endpoints are only accessible to users with credentials. - -**Testing:** -1. When requesting the connector's self-description, the included catalog gives information about available resources. The resource id (e.g. https://w3id.org/idsa/autogen/dataResource/[UUID]) is essential for requesting an artifact or description. -2. The open endpoint at `/api/ids/data` expects an ArtifactRequestMessage with a known resource id as RequestedArtifact (for requesting data) or a DescriptionRequestMessage with a known resource id as RequestedElement (for requesting metadata). - * If this parameter is not known to the connector, you will receive a RejectionMessage as response. - * If the RequestedElement is missing at a DescriptionRequestMessage, you will receive the connector's self-description. - * When sending a simple RequestMessage, you will receive an echo response containing your message body. -3. The running connector offers two data resources. One contains a simple string, the other one a base64 encoded image. - - -## Development - -If you want to setup the connector application yourself, follow the instructions below. If you encounter any problems, please have a look at the [FAQ](https://github.com/FraunhoferISST/Dataspace-Connector/wiki/Frequently-Asked-Questions). - -### Configurations - -The resource folder `conf` provides three important files that are loaded at application start: - -* `keystore-localhost.p12`: The provided keystore, on the one hand, is used as IDS certificate that is loaded by the IDS Framework for requesting a valid Dynamic Attribute Token (DAT) from the Dynamic Attribute Provisioning Service (DAPS). -Each message to IDS participant needs to be signed with a valid DAT. On the other hand, it is used as SSL certificate for TLS encryption. -* `truststore.p12`: The truststore is used by the IDS Framework for any Https communication. It ensures the connection to trusted addresses. -* `config.json`: The configuration is used to set important properties for IDS message handling. - -**Step 1**: When starting the application, the `config.json` will be scanned for important connector information, e.g. its UUID, its address, contact information, or proxy settings. -Please keep this file up to date to your own connector settings. In case you are using the demo cert, you don't need to change anything except the [**proxy settings**](#proxy). - -**If you want to connect to a running connector or any other system running at `https`, keep in mind that you need to add the keystore to your truststore. -Otherwise the communication will fail. For now, with the provided truststore, the Dataspace Connector will accept its own localhost certificate, public certificates, and any IDS keystore that was provided by the Fraunhofer AISEC.** - -_If you are not familiar with the IDS Information Model, the `MainController` class provides an endpoint `GET /example/configuration` to print a filled in Java object as JSON-LD. Adapt this to your needs, take the received string and place it in the `config.json`._ - -**Step 2**: In the provided `config.json`, the `ids:connectorDeployMode` is set to `idsc:TEST_DEPLOYMENT`. This allows to use the `keystore-localhost.p12` as an IDS certificate. -For testing purpose, the existing cert can be used, as on application start, the IDS Framework will not get a valid DAT from the DAPS and for received messages, the sent DAT will not be checked. - -To turn on the DAT checking, you need to set the `ids:connectorDeployMode` to `idsc:PRODUCTIVE_DEPLOYMENT`. For getting a trusted certificate, contact [Gerd Brost](mailto:gerd.brost@aisec.fraunhofer.de). -Add the keystore with the IDS certificate inside to the `resources/conf` and change the filename at `ids:keyStore` accordingly. - -**The TEST_DEPLOYMENT and accepting a demo cert is for testing purposes only! This mode is a security risk and cannot ensure that the connector is talking to a verified IDS participant. Furthermore, messages from the Dataspace Connector without a valid IDS certificate will not be accepted by other connectors.** - -**Step 3 (optional)**: The `application.properties` specifies database, SSL, spring security, open API, and DAPS configurations. - To define on which port the connector should be running, change `server.port={PORT}`. - If you want to add your own SSL certificate, check the corresponding path. - -_As the provided certificate only supports the application running at `localhost`, you may replace this with your IDS keystore, if you want to host the connector in a productive environment._ - -#### Proxy - -For outgoing requests, the connector needs information about an existing system proxy that needs to be set in the `src/main/resources/conf/config.json`. - -``` -"ids:connectorProxy" : [ { - "@type" : "ids:Proxy", - "@id" : "https://w3id.org/idsa/autogen/proxy/548dc73a-ccfb-4039-9569-4b8e219b90bc", - "ids:proxyAuthentication" : { - "@type" : "ids:BasicAuthentication", - "@id" : "https://w3id.org/idsa/autogen/basicAuthentication/47e3cd59-d351-4f5b-99fc-561c94bad5e1" - }, - "ids:proxyURI" : { - "@id" : "http://host:port" - }, - "ids:noProxy" : [ { - "@id" : "https://localhost:8080/" - }, { - "@id" : "http://localhost:8080/" - } ] - } ] -``` - -Check if your system is running behind a proxy. If this is the case, specify the `ids:proxyURI` and change `ids:noProxy` if necessary. Otherwise, delete the key `ids:connectorProxy` and its values. - - -#### Authentication -The application uses HTTP Basic Authentication. Each endpoint behind `/admin/**` needs a user authentication. - -Have a look at the blocked endpoints in the `ConfigurationAdapter` class to add or change endpoints yourself. -In case you don't want to provide authentication for your backend maintenance, feel free to remove the corresponding lines. - -If you want to change the default credentials, go to `application.properties`. The properties are located at `spring.security.user.name=admin` and `spring.security.user.name=password`. - -#### Database - -The Dataspace Connector uses Spring Data JPA to set up the database and manage interactions with it. Spring Data JPA -supports many well-known relational databases out of the box. Thus, the internal H2 can be replaced by e.g. MySQL, -PostgreSQL, or Oracle databases with minimal effort. - -To use another database for the Connector, follow these steps: [Database Configuration](https://github.com/FraunhoferISST/DataspaceConnector/wiki/Database-configuration) - -### Deployment - -In the following, the deployment with Maven and Docker will be explained. - -#### Maven Build - -If you want to build and run locally, ensure that Java 11 is installed. Then, follow these steps: - -1. Execute `cd dataspace-connector` and `mvn clean package`. -2. The connector can be started by running the Spring Boot Application. Therefore, navigate to `/target` and run `java -jar dataspace-connector-{VERSION}.jar`. -3. If everything worked fine, the connector is available at https://localhost:8080/. By default, it is running with an h2 database. - -_After successfully building the project, the Javadocs as a static website can be found at `/target/apidocs`. Open the `index.html` in a browser of your choice._ - -#### Docker Setup - -If you want to deploy in docker and build the maven project with the Dockerfile, follow these steps: - -**Option 1: Build and run Docker image** -1. Navigate to `dataspace-connector`. To build the image, run `docker build -t .` (e.g. `docker build -t dataspaceconnector .`). -2. For running your image as a container, follow [these](https://docs.docker.com/get-started/part2/) instructions: `docker run --publish 8080:8080 --detach --name bb ` - -**Option 2: Using Docker Compose** -1. The `docker-compose.yml` sets up the connector application and a PostgreSQL database. If necessary, make your changes in the `connector.env` and `postgres.env`. Please find more details about setting up different databases [here](https://github.com/FraunhoferISST/DataspaceConnector/wiki/Database-Configuration). -2. If you are starting the application for the very first time, change `spring.jpa.hibernate.ddl-auto=update` in the `application.properties` to `spring.jpa.hibernate.ddl-auto=create`. -3. For starting the application, run `docker-compose up`. Have a look at the `docker-compose.yaml` and make your own configurations if necessary. -4. For any further container starts, reset the setting of Step 2 to `update`. **Otherwise, changes in the database will be lost and overwritten.** Rebuild the image by running `docker-compose build --no-cache` and then follow Step 3. - -If you just want to run a built jar file (with an H2 database) inside a docker image, have a look at the `Dockerfile` inside the [`docker-setup.zip`](https://github.com/FraunhoferISST/DataspaceConnector/releases). - -#### Run Tests - -Tests will be executed automatically when running Maven commands `package`, `verify`, `install`, `site`, or `deploy`. An overview of the implemented test classes is placed at [Supported Test Classes](https://github.com/FraunhoferISST/Dataspace-Connector/wiki/Supported-Test-Classes). - -To run tests manually, execute the following commands in the root directory of the project: - -Run all tests -``` -mvn test -``` -Run specific test class: -``` -mvn test -Dtest=[full class name] -mvn test -Dtest=de.fraunhofer.isst.dataspaceconnector.integrationtest.SelfDescriptionTest -``` - -Run a specific test case (single method) -``` -mvn test -Dtest=[full class name]#[method name] -mvn test -Dtest=de.fraunhofer.isst.dataspaceconnector.integrationtest.SelfDescriptionTest#getSelfDescription_noResources -``` - - -### Backend API - -The OpenApi documentation can be viewed at https://localhost:8080/admin/api. -The JSON representation is available at https://localhost:8080/v3/api-docs. -The .yaml file can be downloaded at https://localhost:8080/v3/api-docs.yaml. - -**OpenApi** - -The connector provides several endpoints for resource database handling and IDS messaging. Details on how to interact with them can be found at [Hands-on IDS Communication](https://github.com/FraunhoferISST/Dataspace-Connector/wiki/Hands-on-IDS-Communication). - -* `Connector: Selfservice` provides information about the running connector -* `Connector: Resource Handling` provides endpoints for local data resource management (register, delete, update data/metadata, and load metadata) -* `Backend: Resource Data Handling` provides endpoints for local data management (register, delete, update data, and load data) -* `Connector: IDS Connector Communication` provides endpoints for requesting artifact (data) and descriptions (metadata) from an external connector (ArtifactRequestMessage, DescriptionRequestMessage) -* `Connector: IDS Broker Communication` provides endpoints for IDS broker messages (ConnectorAvailableMessage, ConnectorUnavailableMessage, ConnectorInactiveMessage, ConnectorUpdateMessage, QueryMessage) - -Next to the ones accessible by using the Swagger UI, the connector, respectively the IDS Framework, provides an IDS endpoint for handling incoming data requests at `/api/ids/data`. - -**Database** - -The data resources are persisted in an H2 database. - -* Local datasource: `/target/db/resources` -* Console path: https://localhost:8080/admin/h2 - +

+
+ Dataspace Connector Logo +
+ Dataspace Connector +
+

+ + +

+ Contact • + Contribute • + Docs • + Issues • + License +

+ + +The Dataspace Connector is an implementation of an IDS connector component following the +[IDS Reference Architecture Model](https://www.internationaldataspaces.org/wp-content/uploads/2019/03/IDS-Reference-Architecture-Model-3.0.pdf). +It integrates the [IDS Information Model](https://github.com/International-Data-Spaces-Association/InformationModel) +and uses the [IDS Connector Framework](https://github.com/FraunhoferISST/IDS-Connector-Framework) +for IDS functionalities and message handling. +The core component in this repository provides a REST API for loading, updating, and deleting +resources with local or remote data enriched by its metadata. It supports IDS conform message +handling with other IDS connectors and components and implements usage control for selected IDS +usage policy patterns. + +*** + +

+ + Manual & Documentation + +

+ +*** + +## Quick Start + +If you want to build and run locally, ensure that at least Java 11 is installed. Then, follow these steps: + +1. Clone this repository. +2. Execute `cd DataspaceConnector` and `./mvnw clean package`. +3. Navigate to `/target` and run `java -jar dataspaceconnector-{VERSION}.jar`. +4. If everything worked fine, the connector is available at https://localhost:8080/. The API can + be accessed at https://localhost:8080/api. The Swagger UI can be found at https://localhost:8080/api/docs. + +For more details, see [here](https://international-data-spaces-association.github.io/DataspaceConnector/). + +## Contributing + +You are very welcome to contribute to this project when you find a bug, want to suggest an +improvement, or have an idea for a useful feature. Please find a set of guidelines at the +[CONTRIBUTING.md](CONTRIBUTING.md) and the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +## Developers + +This is an ongoing project of the [Data Economy](https://www.isst.fraunhofer.de/en/business-units/data-economy.html) +business unit of the [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html). + +The core development is driven by +* [Heinrich Pettenpohl](https://github.com/HeinrichPet), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html), Project Manager +* [Julia Pampus](https://github.com/juliapampus), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html), Lead Developer +* [Brian-Frederik Jahnke](https://github.com/brianjahnke), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [Ronja Quensel](https://github.com/ronjaquensel), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) + +with significant contributions, comments, and support by (in alphabetical order): +* [Haydar Qarawlus](https://github.com/hqarawlus), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [Johannes Pieperbeck](https://github.com/jpieperbeck), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [René Brinkhege](https://github.com/renebrinkhege), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [Steffen Biehs](https://github.com/steffen-biehs), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) ## License Copyright © 2020 Fraunhofer ISST. This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) for details. diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 000000000..f5818f0a1 --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,67 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: dataspace-connector + name: dataspace-connector +spec: + replicas: 1 + selector: + matchLabels: + app: dataspace-connector + template: + metadata: + labels: + app: dataspace-connector + spec: + containers: + - image: localhost:5000/dataspace-connector:latest + imagePullPolicy: IfNotPresent + name: dataspace-connector + ports: + - containerPort: 8080 + env: + - name: SPRING_DATASOURCE_URL + value: "jdbc:postgresql://postgres:5432/test" + - name: SPRING_DATASOURCE_USERNAME + value: "admin" + - name: SPRING_DATASOURCE_PASSWORD + value: "password" + - name: SPRING_DATASOURCE_PLATFORM + value: "postgres" + - name: SPRING_DATASOURCE_DRIVER-CLASS-NAME + value: "org.postgresql.Driver" + - name: SPRING_JPA_DATABASE-PLATFORM + value: "org.hibernate.dialect.PostgreSQLDialect" + - name: CONFIGURATION_PATH + value: "file:///connector-certs/config.json" + volumeMounts: + - name: certs-volume + mountPath: /connector-certs + resources: + requests: + memory: 512Mi + cpu: 0.1 + limits: + memory: 2Gi + cpu: 1 + volumes: + - name: certs-volume + secret: + secretName: dataspace-connector-certs diff --git a/docker-compose.yml b/docker-compose.yml index 46074f643..6cb50e5e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,24 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + version: '3.5' services: postgres: - image: postgres + image: postgres:13 container_name: 'postgres-container' ports: - "5432:5432" @@ -10,6 +26,9 @@ services: - ./postgres.env networks: - connector + volumes: + - connector-data:/var/lib/postgresql/data + connector: build: context: . @@ -25,3 +44,6 @@ services: networks: connector: + +volumes: + connector-data: {} diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 000000000..5dc073f5a --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,9 @@ +package.json +package-lock.json +_site +assets/css/just-the-docs-default.scss +assets/css/just-the-docs-light.scss +assets/css/just-the-docs-dark.scss +assets/js/vendor/lunr.min.js +assets/js/search-data.json +assets/js/just-the-docs.js diff --git a/docs/.prettierrc b/docs/.prettierrc new file mode 100644 index 000000000..284e842b0 --- /dev/null +++ b/docs/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "semi": false, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5" +} + diff --git a/docs/.stylelintrc.json b/docs/.stylelintrc.json new file mode 100644 index 000000000..329ca3425 --- /dev/null +++ b/docs/.stylelintrc.json @@ -0,0 +1,13 @@ +{ + "ignoreFiles": [ + "assets/css/just-the-docs-default.scss", + "assets/css/just-the-docs-light.scss", + "assets/css/just-the-docs-dark.scss", + "_sass/vendor/**/*.scss" + ], + "extends": ["stylelint-config-primer", "stylelint-config-prettier"], + "plugins": ["stylelint-prettier"], + "rules": { + "prettier/prettier": true + } +} diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 000000000..5fde96683 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,11 @@ +--- +layout: default +title: 404 +permalink: /404 +nav_exclude: true +search_exclude: true +--- + +

Page not found

+ +

The page you requested could not be found. Try using the navigation {% if site.search_enabled != false %}or search {% endif %}to find what you're looking for or go to this site's home page.

diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..77c27685b --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,66 @@ +remote_theme: pmarsceill/just-the-docs +title: Dataspace Connector +description: Dataspace Connector documentation +baseurl: "/DataspaceConnector" +url: "https://international-data-spaces-association.github.io" + +# Color scheme currently only supports "dark", "light"/nil (default), or a custom scheme that you define +color_scheme: custom +permalink: pretty + +# Set a path/url to a logo that will be displayed instead of the title +# logo: "/assets/images/dsc_logo.png" + +# Enable or disable the site search +# Supports true (default) or false +search_enabled: true + +search: + # Split pages into sections that can be searched individually + # Supports 1 - 6, default: 2 + heading_level: 2 + # Maximum amount of previews per search result + # Default: 3 + previews: 2 + # Maximum amount of words to display before a matched word in the preview + # Default: 5 + preview_words_before: 3 + # Maximum amount of words to display after a matched word in the preview + # Default: 10 + preview_words_after: 3 + # Set the search token separator + # Default: /[\s\-/]+/ + # Example: enable support for hyphenated search words + tokenizer_separator: /[\s/]+/ + # Display the relative url in search results + # Supports true (default) or false + rel_url: true + # Enable or disable the search button that appears in the bottom right corner of every page + # Supports true or false (default) + button: false + +# Enable or disable heading anchors +heading_anchors: true + +# Aux links for the upper right navigation +aux_links: + "Homepage": + - "https://dataspace-connector.io" + +# Makes Aux links open in a new tab. Default is false +aux_links_new_tab: true + +# Sort order for navigation links +# nav_sort: case_insensitive # default, equivalent to nil +nav_sort: case_sensitive # Capital letters sorted before lowercase + +# Back to top link +back_to_top: true +back_to_top_text: "Back to top" + +footer_content: "Copyright © 2021 Fraunhofer ISST." +# Distributed by an MIT license." + +# Footer last edited timestamp +last_edit_timestamp: true # show or hide edit time - page must have `last_modified_date` defined in the frontmatter +last_edit_time_format: "%b %e %Y at %I:%M %p" # uses ruby's time format: https://ruby-doc.org/stdlib-2.7.0/libdoc/time/rdoc/Time.html diff --git a/docs/_sass/color_schemes/custom.scss b/docs/_sass/color_schemes/custom.scss new file mode 100644 index 000000000..50b2d51c2 --- /dev/null +++ b/docs/_sass/color_schemes/custom.scss @@ -0,0 +1,17 @@ +$body-background-color: #fff; +$sidebar-color: #efefef; +$border-color: $grey-lt-100; + +$body-text-color: $grey-dk-100; +$body-heading-color: $grey-dk-200; +$nav-child-link-color: $grey-dk-100; +$search-result-preview-color: $grey-dk-000; + +$link-color: #009374; +$btn-primary-color: #009374; +$base-button-color: $grey-lt-200; + +$code-background-color: $grey-lt-000; +$search-background-color: $grey-lt-000; +$table-background-color: $grey-lt-000; +$feedback-color: darken($sidebar-color, 3%); diff --git a/docs/assets/css/just-the-docs-default.css b/docs/assets/css/just-the-docs-default.css new file mode 100644 index 000000000..d46af4fcd --- /dev/null +++ b/docs/assets/css/just-the-docs-default.css @@ -0,0 +1,8169 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +main { + display: block +} + +h1 { + font-size: 2em; + margin: 0.67em 0 +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +pre { + font-family: monospace, monospace; + font-size: 1em +} + +a { + background-color: transparent +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b, strong { + font-weight: bolder +} + +code, kbd, samp { + font-family: monospace, monospace; + font-size: 1em +} + +small { + font-size: 80% +} + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -0.25em +} + +sup { + top: -0.5em +} + +img { + border-style: none +} + +button, input, optgroup, select, textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0 +} + +button, input { + overflow: visible +} + +button, select { + text-transform: none +} + +button, [type="button"], [type="reset"], [type="submit"] { + -webkit-appearance: button +} + +button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0 +} + +button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText +} + +fieldset { + padding: 0.35em 0.75em 0.625em +} + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} + +progress { + vertical-align: baseline +} + +textarea { + overflow: auto +} + +[type="checkbox"], [type="radio"] { + box-sizing: border-box; + padding: 0 +} + +[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { + height: auto +} + +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} + +details { + display: block +} + +summary { + display: list-item +} + +template { + display: none +} + +[hidden] { + display: none +} + +* { + box-sizing: border-box +} + +::selection { + color: #fff; + background: #009374 +} + +html { + font-size: 14px !important; + scroll-behavior: smooth +} + +@media (min-width: 31.25rem) { + html { + font-size: 16px !important + } +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: inherit; + line-height: 1.4; + color: #5c5962; + background-color: #fff +} + +ol, ul, dl, pre, address, blockquote, table, div, hr, form, fieldset, noscript .table-wrapper { + margin-top: 0 +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 1em; + font-weight: 500; + line-height: 1.25; + color: #44434d +} + +p { + margin-top: 1em; + margin-bottom: 1em +} + +a { + color: #009374; + text-decoration: none +} + +a:not([class]) { + text-decoration: none; + background-image: linear-gradient(#eeebee 0%, #eeebee 100%); + background-repeat: repeat-x; + background-position: 0 100%; + background-size: 1px 1px +} + +a:not([class]):hover { + background-image: linear-gradient(rgba(0, 147, 116, 0.45) 0%, rgba(0, 147, 116, 0.45) 100%); + background-size: 1px 1px +} + +code { + font-family: "SFMono-Regular", Menlo, Consolas, Monospace; + font-size: 0.75em; + line-height: 1.4 +} + +figure, pre { + margin: 0 +} + +li { + margin: 0.25em 0 +} + +img { + max-width: 100%; + height: auto +} + +hr { + height: 1px; + padding: 0; + margin: 2rem 0; + background-color: #eeebee; + border: 0 +} + +.side-bar { + z-index: 0; + display: flex; + flex-wrap: wrap; + background-color: #f5f6fa +} + +@media (min-width: 50rem) { + .side-bar { + flex-wrap: nowrap; + position: fixed; + width: 248px; + height: 100%; + flex-direction: column; + border-right: 1px solid #eeebee; + align-items: flex-end + } +} + +@media (min-width: 66.5rem) { + .side-bar { + width: calc((100% - 1064px) / 2 + 264px); + min-width: 264px + } +} + +@media (min-width: 50rem) { + .main { + position: relative; + max-width: 800px; + margin-left: 248px + } +} + +@media (min-width: 66.5rem) { + .main { + margin-left: calc((100% - 1064px) / 2 + 264px) + } +} + +.main-content-wrap { + padding-right: 1rem; + padding-left: 1rem; + padding-top: 1rem; + padding-bottom: 1rem +} + +@media (min-width: 50rem) { + .main-content-wrap { + padding-right: 2rem; + padding-left: 2rem + } +} + +@media (min-width: 50rem) { + .main-content-wrap { + padding-top: 2rem; + padding-bottom: 2rem + } +} + +.main-header { + z-index: 0; + display: none; + background-color: #f5f6fa +} + +@media (min-width: 50rem) { + .main-header { + display: flex; + justify-content: space-between; + height: 60px; + background-color: #fff; + border-bottom: 1px solid #eeebee + } +} + +.main-header.nav-open { + display: block +} + +@media (min-width: 50rem) { + .main-header.nav-open { + display: flex + } +} + +.site-nav, .site-header, .site-footer { + width: 100% +} + +@media (min-width: 66.5rem) { + .site-nav, .site-header, .site-footer { + width: 264px + } +} + +.site-nav { + display: none +} + +.site-nav.nav-open { + display: block +} + +@media (min-width: 50rem) { + .site-nav { + display: block; + padding-top: 3rem; + padding-bottom: 1rem; + overflow-y: auto; + flex: 1 1 auto + } +} + +.site-header { + display: flex; + min-height: 60px; + align-items: center +} + +@media (min-width: 50rem) { + .site-header { + height: 60px; + max-height: 60px; + border-bottom: 1px solid #eeebee + } +} + +.site-title { + padding-right: 1rem; + padding-left: 1rem; + flex-grow: 1; + display: flex; + height: 100%; + align-items: center; + padding-top: .75rem; + padding-bottom: .75rem; + color: #44434d; + font-size: 18px !important +} + +@media (min-width: 50rem) { + .site-title { + padding-right: 2rem; + padding-left: 2rem + } +} + +@media (min-width: 31.25rem) { + .site-title { + font-size: 20px !important; + line-height: 1.25; + } +} + +@media (min-width: 50rem) { + .site-title { + padding-top: .5rem; + padding-bottom: .5rem + } +} + +.site-button { + display: flex; + height: 100%; + padding: 1rem; + align-items: center +} + +@media (min-width: 50rem) { + .site-header .site-button { + display: none + } +} + +.site-title:hover { + background-image: linear-gradient(-90deg, #ebedf5 0%, rgba(235, 237, 245, 0.8) 80%, rgba(235, 237, 245, 0) 100%) +} + +.site-button:hover { + background-image: linear-gradient(-90deg, #ebedf5 0%, rgba(235, 237, 245, 0.8) 100%) +} + +body { + position: relative; + padding-bottom: 4rem; + overflow-y: scroll +} + +@media (min-width: 50rem) { + body { + position: static; + padding-bottom: 0 + } +} + +.site-footer { + padding-right: 1rem; + padding-left: 1rem; + position: absolute; + bottom: 0; + left: 0; + padding-top: 1rem; + padding-bottom: 1rem; + color: #959396; + font-size: 11px !important +} + +@media (min-width: 50rem) { + .site-footer { + padding-right: 2rem; + padding-left: 2rem + } +} + +@media (min-width: 31.25rem) { + .site-footer { + font-size: 12px !important + } +} + +@media (min-width: 50rem) { + .site-footer { + position: static; + justify-self: end + } +} + +.icon { + width: 1.5rem; + height: 1.5rem; + color: #009374 +} + +.main-content { + line-height: 1.6 +} + +.main-content ol, .main-content ul, .main-content dl, .main-content pre, .main-content address, .main-content blockquote, .main-content .table-wrapper { + margin-top: 0.5em +} + +.main-content a { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.main-content ul, .main-content ol { + padding-left: 1.5em +} + +.main-content li .highlight { + margin-top: .25rem +} + +.main-content ol { + list-style-type: none; + counter-reset: step-counter +} + +.main-content ol > li { + position: relative +} + +.main-content ol > li::before { + position: absolute; + top: 0.2em; + left: -1.6em; + color: #959396; + content: counter(step-counter); + counter-increment: step-counter; + font-size: 12px !important +} + +@media (min-width: 31.25rem) { + .main-content ol > li::before { + font-size: 14px !important + } +} + +@media (min-width: 31.25rem) { + .main-content ol > li::before { + top: 0.11em + } +} + +.main-content ol > li ol { + counter-reset: sub-counter +} + +.main-content ol > li ol li::before { + content: counter(sub-counter, lower-alpha); + counter-increment: sub-counter +} + +.main-content ul { + list-style: none +} + +.main-content ul > li::before { + position: absolute; + margin-left: -1.4em; + color: #959396; + content: "•" +} + +.main-content .task-list { + padding-left: 0 +} + +.main-content .task-list-item { + display: flex; + align-items: center +} + +.main-content .task-list-item::before { + content: "" +} + +.main-content .task-list-item-checkbox { + margin-right: 0.6em +} + +.main-content hr + * { + margin-top: 0 +} + +.main-content h1:first-of-type { + margin-top: 0.5em +} + +.main-content dl { + display: grid; + grid-template:auto / 10em 1fr +} + +.main-content dt, .main-content dd { + margin: 0.25em 0 +} + +.main-content dt { + grid-column: 1; + font-weight: 500; + text-align: right +} + +.main-content dt::after { + content: ":" +} + +.main-content dd { + grid-column: 2; + margin-bottom: 0; + margin-left: 1em +} + +.main-content dd blockquote:first-child, .main-content dd div:first-child, .main-content dd dl:first-child, .main-content dd dt:first-child, .main-content dd h1:first-child, .main-content dd h2:first-child, .main-content dd h3:first-child, .main-content dd h4:first-child, .main-content dd h5:first-child, .main-content dd h6:first-child, .main-content dd li:first-child, .main-content dd ol:first-child, .main-content dd p:first-child, .main-content dd pre:first-child, .main-content dd table:first-child, .main-content dd ul:first-child, .main-content dd .table-wrapper:first-child { + margin-top: 0 +} + +.main-content dd dl:first-child dt:first-child, .main-content dd dl:first-child dd:nth-child(2), .main-content ol dl:first-child dt:first-child, .main-content ol dl:first-child dd:nth-child(2), .main-content ul dl:first-child dt:first-child, .main-content ul dl:first-child dd:nth-child(2) { + margin-top: 0 +} + +.main-content .anchor-heading { + position: absolute; + right: -1rem; + width: 1.5rem; + height: 100%; + padding-right: .25rem; + padding-left: .25rem; + overflow: visible +} + +@media (min-width: 50rem) { + .main-content .anchor-heading { + right: auto; + left: -1.5rem + } +} + +.main-content .anchor-heading svg { + display: inline-block; + width: 100%; + height: 100%; + color: #009374; + visibility: hidden +} + +.main-content .anchor-heading:hover svg, .main-content h1:hover > .anchor-heading svg, .main-content h2:hover > .anchor-heading svg, .main-content h3:hover > .anchor-heading svg, .main-content h4:hover > .anchor-heading svg, .main-content h5:hover > .anchor-heading svg, .main-content h6:hover > .anchor-heading svg { + visibility: visible +} + +.main-content summary { + cursor: pointer +} + +.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 { + position: relative; + margin-top: 1.5em; + margin-bottom: 0.25em +} + +.main-content h1:first-child, .main-content h2:first-child, .main-content h3:first-child, .main-content h4:first-child, .main-content h5:first-child, .main-content h6:first-child { + margin-top: .5rem +} + +.main-content h1 + table, .main-content h1 + .table-wrapper, .main-content h1 + .code-example, .main-content h1 + .highlighter-rouge, .main-content h2 + table, .main-content h2 + .table-wrapper, .main-content h2 + .code-example, .main-content h2 + .highlighter-rouge, .main-content h3 + table, .main-content h3 + .table-wrapper, .main-content h3 + .code-example, .main-content h3 + .highlighter-rouge, .main-content h4 + table, .main-content h4 + .table-wrapper, .main-content h4 + .code-example, .main-content h4 + .highlighter-rouge, .main-content h5 + table, .main-content h5 + .table-wrapper, .main-content h5 + .code-example, .main-content h5 + .highlighter-rouge, .main-content h6 + table, .main-content h6 + .table-wrapper, .main-content h6 + .code-example, .main-content h6 + .highlighter-rouge { + margin-top: 1em +} + +.main-content h1 + p, .main-content h2 + p, .main-content h3 + p, .main-content h4 + p, .main-content h5 + p, .main-content h6 + p { + margin-top: 0 +} + +.nav-list { + padding: 0; + margin-top: 0; + margin-bottom: 0; + list-style: none +} + +.nav-list .nav-list-item { + font-size: 14px !important; + position: relative; + margin: 0 +} + +@media (min-width: 31.25rem) { + .nav-list .nav-list-item { + font-size: 16px !important + } +} + +@media (min-width: 50rem) { + .nav-list .nav-list-item { + font-size: 12px !important + } +} + +@media (min-width: 50rem) and (min-width: 31.25rem) { + .nav-list .nav-list-item { + font-size: 14px !important + } +} + +.nav-list .nav-list-item .nav-list-link { + display: block; + min-height: 3rem; + padding-top: .25rem; + padding-bottom: .25rem; + line-height: 2.5rem; + padding-right: 3rem; + padding-left: 1rem +} + +@media (min-width: 50rem) { + .nav-list .nav-list-item .nav-list-link { + min-height: 2rem; + line-height: 1.5rem; + padding-right: 2rem; + padding-left: 2rem + } +} + +.nav-list .nav-list-item .nav-list-link.active { + font-weight: 600; + text-decoration: none +} + +.nav-list .nav-list-item .nav-list-link:hover, .nav-list .nav-list-item .nav-list-link.active { + background-image: linear-gradient(-90deg, #ebedf5 0%, rgba(235, 237, 245, 0.8) 80%, rgba(235, 237, 245, 0) 100%) +} + +.nav-list .nav-list-item .nav-list-expander { + position: absolute; + right: 0; + width: 3rem; + height: 3rem; + padding-top: .75rem; + padding-right: .75rem; + padding-bottom: .75rem; + padding-left: .75rem; + color: #009374 +} + +@media (min-width: 50rem) { + .nav-list .nav-list-item .nav-list-expander { + width: 2rem; + height: 2rem; + padding-top: .5rem; + padding-right: .5rem; + padding-bottom: .5rem; + padding-left: .5rem + } +} + +.nav-list .nav-list-item .nav-list-expander:hover { + background-image: linear-gradient(-90deg, #ebedf5 0%, rgba(235, 237, 245, 0.8) 100%) +} + +.nav-list .nav-list-item .nav-list-expander svg { + transform: rotate(90deg) +} + +.nav-list .nav-list-item > .nav-list { + display: none; + padding-left: .75rem; + list-style: none +} + +.nav-list .nav-list-item > .nav-list .nav-list-item { + position: relative +} + +.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-link { + color: #5c5962 +} + +.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-expander { + color: #5c5962 +} + +.nav-list .nav-list-item.active > .nav-list-expander svg { + transform: rotate(-90deg) +} + +.nav-list .nav-list-item.active > .nav-list { + display: block +} + +.nav-category { + padding-top: .5rem; + padding-right: 1rem; + padding-bottom: .5rem; + padding-left: 1rem; + font-weight: 600; + text-align: end; + text-transform: uppercase; + border-bottom: 1px solid #eeebee; + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .nav-category { + font-size: 12px !important + } +} + +@media (min-width: 50rem) { + .nav-category { + padding-right: 2rem; + padding-left: 2rem; + margin-top: 1rem; + text-align: start + } + + .nav-category:first-child { + margin-top: 0 + } +} + +.aux-nav { + height: 100%; + overflow-x: auto; + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .aux-nav { + font-size: 12px !important + } +} + +.aux-nav .aux-nav-list { + display: flex; + height: 100%; + padding: 0; + margin: 0; + list-style: none +} + +.aux-nav .aux-nav-list-item { + display: inline-block; + height: 100%; + padding: 0; + margin: 0 +} + +@media (min-width: 50rem) { + .aux-nav { + padding-right: 1rem + } +} + +@media (min-width: 50rem) { + .breadcrumb-nav { + margin-top: -1rem + } +} + +.breadcrumb-nav-list { + padding-left: 0; + margin-bottom: .75rem; + list-style: none +} + +.breadcrumb-nav-list-item { + display: table-cell; + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .breadcrumb-nav-list-item { + font-size: 12px !important + } +} + +.breadcrumb-nav-list-item::before { + display: none +} + +.breadcrumb-nav-list-item::after { + display: inline-block; + margin-right: .5rem; + margin-left: .5rem; + color: #959396; + content: "/" +} + +.breadcrumb-nav-list-item:last-child::after { + content: "" +} + +h1, .text-alpha { + font-size: 32px !important; + line-height: 1.25; + font-weight: 300 +} + +@media (min-width: 31.25rem) { + h1, .text-alpha { + font-size: 36px !important + } +} + +h2, .text-beta { + font-size: 18px !important +} + +@media (min-width: 31.25rem) { + h2, .text-beta { + font-size: 24px !important; + line-height: 1.25 + } +} + +h3, .text-gamma { + font-size: 16px !important +} + +@media (min-width: 31.25rem) { + h3, .text-gamma { + font-size: 18px !important + } +} + +h4, .text-delta { + font-size: 11px !important; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.1em +} + +@media (min-width: 31.25rem) { + h4, .text-delta { + font-size: 12px !important + } +} + +h4 code { + text-transform: none +} + +h5, .text-epsilon { + font-size: 12px !important; + color: #44434d +} + +@media (min-width: 31.25rem) { + h5, .text-epsilon { + font-size: 14px !important + } +} + +h6, .text-zeta { + font-size: 11px !important; + color: #44434d +} + +@media (min-width: 31.25rem) { + h6, .text-zeta { + font-size: 12px !important + } +} + +.text-small { + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .text-small { + font-size: 12px !important + } +} + +.text-mono { + font-family: "SFMono-Regular", Menlo, Consolas, Monospace !important +} + +.text-left { + text-align: left !important +} + +.text-center { + text-align: center !important +} + +.text-right { + text-align: right !important +} + +.label, .label-blue { + display: inline-block; + padding-top: 0.16em; + padding-right: 0.56em; + padding-bottom: 0.16em; + padding-left: 0.56em; + margin-right: .5rem; + margin-left: .5rem; + color: #fff; + text-transform: uppercase; + vertical-align: middle; + background-color: #2869e6; + font-size: 11px !important; + border-radius: 12px +} + +@media (min-width: 31.25rem) { + .label, .label-blue { + font-size: 12px !important + } +} + +.label-green { + background-color: #009c7b +} + +.label-purple { + background-color: #5e41d0 +} + +.label-red { + background-color: #e94c4c +} + +.label-yellow { + color: #44434d; + background-color: #f7d12e +} + +.btn { + display: inline-block; + box-sizing: border-box; + padding-top: 0.3em; + padding-right: 1em; + padding-bottom: 0.3em; + padding-left: 1em; + margin: 0; + font-family: inherit; + font-size: inherit; + font-weight: 500; + line-height: 1.5; + color: #009374; + text-decoration: none; + vertical-align: baseline; + cursor: pointer; + background-color: #ecebed; + border-width: 0; + border-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); + appearance: none +} + +.btn:focus { + text-decoration: none; + outline: none; + box-shadow: 0 0 0 3px rgba(0, 0, 255, 0.25) +} + +.btn:focus:hover, .btn.selected:focus { + box-shadow: 0 0 0 3px rgba(0, 0, 255, 0.25) +} + +.btn:hover, .btn.zeroclipboard-is-hover { + color: #00896c +} + +.btn:hover, .btn:active, .btn.zeroclipboard-is-hover, .btn.zeroclipboard-is-active { + text-decoration: none; + background-color: #e9e8eb +} + +.btn:active, .btn.selected, .btn.zeroclipboard-is-active { + background-color: #e4e3e6; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15) +} + +.btn.selected:hover { + background-color: #cfcfcf +} + +.btn:disabled, .btn:disabled:hover, .btn.disabled, .btn.disabled:hover { + color: rgba(102, 102, 102, 0.5); + cursor: default; + background-color: rgba(229, 229, 229, 0.5); + background-image: none; + box-shadow: none +} + +.btn-outline { + color: #009374; + background: transparent; + box-shadow: inset 0 0 0 2px #e6e1e8 +} + +.btn-outline:hover, .btn-outline:active, .btn-outline.zeroclipboard-is-hover, .btn-outline.zeroclipboard-is-active { + color: #007f64; + text-decoration: none; + background-color: transparent; + box-shadow: inset 0 0 0 3px #e6e1e8 +} + +.btn-outline:focus { + text-decoration: none; + outline: none; + box-shadow: inset 0 0 0 2px #5c5962, 0 0 0 3px rgba(0, 0, 255, 0.25) +} + +.btn-outline:focus:hover, .btn-outline.selected:focus { + box-shadow: inset 0 0 0 2px #5c5962 +} + +.btn-primary { + color: #fff; + background-color: #00896c; + background-image: linear-gradient(#00ad88, #00896c); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12) +} + +.btn-primary:hover, .btn-primary.zeroclipboard-is-hover { + color: #fff; + background-color: #007f64; + background-image: linear-gradient(#009d7c, #007f64) +} + +.btn-primary:active, .btn-primary.selected, .btn-primary.zeroclipboard-is-active { + background-color: #007a60; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15) +} + +.btn-primary.selected:hover { + background-color: #00604c +} + +.btn-purple { + color: #fff; + background-color: #5739ce; + background-image: linear-gradient(#6f55d5, #5739ce); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12) +} + +.btn-purple:hover, .btn-purple.zeroclipboard-is-hover { + color: #fff; + background-color: #5132cb; + background-image: linear-gradient(#6549d2, #5132cb) +} + +.btn-purple:active, .btn-purple.selected, .btn-purple.zeroclipboard-is-active { + background-color: #4f31c6; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15) +} + +.btn-purple.selected:hover { + background-color: #472cb2 +} + +.btn-blue { + color: #fff; + background-color: #227efa; + background-image: linear-gradient(#4593fb, #227efa); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12) +} + +.btn-blue:hover, .btn-blue.zeroclipboard-is-hover { + color: #fff; + background-color: #1878fa; + background-image: linear-gradient(#368afa, #1878fa) +} + +.btn-blue:active, .btn-blue.selected, .btn-blue.zeroclipboard-is-active { + background-color: #1375f9; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15) +} + +.btn-blue.selected:hover { + background-color: #0669ed +} + +.btn-green { + color: #fff; + background-color: #10ac7d; + background-image: linear-gradient(#13cc95, #10ac7d); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.12) +} + +.btn-green:hover, .btn-green.zeroclipboard-is-hover { + color: #fff; + background-color: #0fa276; + background-image: linear-gradient(#12be8b, #0fa276) +} + +.btn-green:active, .btn-green.selected, .btn-green.zeroclipboard-is-active { + background-color: #0f9e73; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15) +} + +.btn-green.selected:hover { + background-color: #0d8662 +} + +.search { + position: relative; + z-index: 2; + flex-grow: 1; + height: 4rem; + padding: .5rem; + transition: padding linear 200ms +} + +@media (min-width: 50rem) { + .search { + position: relative !important; + width: auto !important; + height: 100% !important; + padding: 0; + transition: none + } +} + +.search-input-wrap { + position: relative; + z-index: 1; + height: 3rem; + overflow: hidden; + border-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); + transition: height linear 200ms +} + +@media (min-width: 50rem) { + .search-input-wrap { + position: absolute; + width: 100%; + max-width: 536px; + height: 100% !important; + border-radius: 0; + box-shadow: none; + transition: width ease 400ms + } +} + +.search-input { + position: absolute; + width: 100%; + height: 100%; + padding-top: .5rem; + padding-right: 1rem; + padding-bottom: .5rem; + padding-left: 2.5rem; + font-size: 16px; + background-color: #f5f6fa; + border-top: 0; + border-right: 0; + border-bottom: 0; + border-left: 0; + border-radius: 0 +} + +@media (min-width: 50rem) { + .search-input { + padding-top: 1rem; + padding-bottom: 1rem; + padding-left: 3.5rem; + font-size: 14px; + background-color: #fff; + transition: padding-left linear 200ms + } +} + +.search-input:focus { + outline: 0 +} + +.search-input:focus + .search-label .search-icon { + color: #009374 +} + +.search-label { + position: absolute; + display: flex; + height: 100%; + padding-left: 1rem +} + +@media (min-width: 50rem) { + .search-label { + padding-left: 2rem; + transition: padding-left linear 200ms + } +} + +.search-label .search-icon { + width: 1.2rem; + height: 1.2rem; + align-self: center; + color: #959396 +} + +.search-results { + position: absolute; + left: 0; + display: none; + width: 100%; + max-height: calc(100% - 4rem); + overflow-y: auto; + background-color: #f5f6fa; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08) +} + +@media (min-width: 50rem) { + .search-results { + top: 100%; + width: 536px; + max-height: calc(100vh - 200%) !important + } +} + +.search-results-list { + padding-left: 0; + margin-bottom: .25rem; + list-style: none; + font-size: 14px !important +} + +@media (min-width: 31.25rem) { + .search-results-list { + font-size: 16px !important + } +} + +@media (min-width: 50rem) { + .search-results-list { + font-size: 12px !important + } +} + +@media (min-width: 50rem) and (min-width: 31.25rem) { + .search-results-list { + font-size: 14px !important + } +} + +.search-results-list-item { + padding: 0; + margin: 0 +} + +.search-result { + display: block; + padding-top: .25rem; + padding-right: .75rem; + padding-bottom: .25rem; + padding-left: .75rem +} + +.search-result:hover, .search-result.active { + background-color: #ebedf5 +} + +.search-result-title { + display: block; + padding-top: .5rem; + padding-bottom: .5rem +} + +@media (min-width: 31.25rem) { + .search-result-title { + display: inline-block; + width: 40%; + padding-right: .5rem; + vertical-align: top + } +} + +.search-result-doc { + display: flex; + align-items: center; + word-wrap: break-word +} + +.search-result-doc.search-result-doc-parent { + opacity: 0.5; + font-size: 12px !important +} + +@media (min-width: 31.25rem) { + .search-result-doc.search-result-doc-parent { + font-size: 14px !important + } +} + +@media (min-width: 50rem) { + .search-result-doc.search-result-doc-parent { + font-size: 11px !important + } +} + +@media (min-width: 50rem) and (min-width: 31.25rem) { + .search-result-doc.search-result-doc-parent { + font-size: 12px !important + } +} + +.search-result-doc .search-result-icon { + width: 1rem; + height: 1rem; + margin-right: .5rem; + color: #009374; + flex-shrink: 0 +} + +.search-result-doc .search-result-doc-title { + overflow: auto +} + +.search-result-section { + margin-left: 1.5rem; + word-wrap: break-word +} + +.search-result-rel-url { + display: block; + margin-left: 1.5rem; + overflow: hidden; + color: #959396; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 9px !important +} + +@media (min-width: 31.25rem) { + .search-result-rel-url { + font-size: 10px !important + } +} + +.search-result-previews { + display: block; + padding-top: .5rem; + padding-bottom: .5rem; + padding-left: 1rem; + margin-left: .5rem; + color: #959396; + word-wrap: break-word; + border-left: 1px solid; + border-left-color: #eeebee; + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .search-result-previews { + font-size: 12px !important + } +} + +@media (min-width: 31.25rem) { + .search-result-previews { + display: inline-block; + width: 60%; + padding-left: .5rem; + margin-left: 0; + vertical-align: top + } +} + +.search-result-preview + .search-result-preview { + margin-top: .25rem +} + +.search-result-highlight { + font-weight: bold +} + +.search-no-result { + padding-top: .5rem; + padding-right: .75rem; + padding-bottom: .5rem; + padding-left: .75rem; + font-size: 12px !important +} + +@media (min-width: 31.25rem) { + .search-no-result { + font-size: 14px !important + } +} + +.search-button { + position: fixed; + right: 1rem; + bottom: 1rem; + display: flex; + width: 3.5rem; + height: 3.5rem; + background-color: #f5f6fa; + border: 1px solid rgba(0, 147, 116, 0.3); + border-radius: 1.75rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); + align-items: center; + justify-content: center +} + +.search-overlay { + position: fixed; + top: 0; + left: 0; + z-index: 1; + width: 0; + height: 0; + background-color: rgba(0, 0, 0, 0.3); + opacity: 0; + transition: opacity ease 400ms, width 0s 400ms, height 0s 400ms +} + +.search-active .search { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 0 +} + +.search-active .search-input-wrap { + height: 4rem; + border-radius: 0 +} + +@media (min-width: 50rem) { + .search-active .search-input-wrap { + width: 536px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08) + } +} + +.search-active .search-input { + background-color: #f5f6fa +} + +@media (min-width: 50rem) { + .search-active .search-input { + padding-left: 2.3rem + } +} + +@media (min-width: 50rem) { + .search-active .search-label { + padding-left: 0.6rem + } +} + +.search-active .search-results { + display: block +} + +.search-active .search-overlay { + width: 100%; + height: 100%; + opacity: 1; + transition: opacity ease 400ms, width 0s, height 0s +} + +@media (min-width: 50rem) { + .search-active .main { + position: fixed; + right: 0; + left: 0 + } +} + +.search-active .main-header { + padding-top: 4rem +} + +@media (min-width: 50rem) { + .search-active .main-header { + padding-top: 0 + } +} + +.table-wrapper { + display: block; + width: 100%; + max-width: 100%; + margin-bottom: 1.5rem; + overflow-x: auto; + border-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08) +} + +table { + display: table; + min-width: 100%; + border-collapse: separate +} + +th, td { + font-size: 12px !important; + min-width: 120px; + padding-top: .5rem; + padding-right: .75rem; + padding-bottom: .5rem; + padding-left: .75rem; + background-color: #f5f6fa; + border-bottom: 1px solid rgba(238, 235, 238, 0.5); + border-left: 1px solid #eeebee +} + +@media (min-width: 31.25rem) { + th, td { + font-size: 14px !important + } +} + +th:first-of-type, td:first-of-type { + border-left: 0 +} + +tbody tr:last-of-type th, tbody tr:last-of-type td { + border-bottom: 0 +} + +tbody tr:last-of-type td { + padding-bottom: .75rem +} + +thead th { + border-bottom: 1px solid #eeebee +} + +code { + padding: 0.2em 0.15em; + font-weight: 400; + background-color: #f5f6fa; + border: 1px solid #eeebee; + border-radius: 4px +} + +a:visited code { + border-color: #eeebee +} + +div.highlighter-rouge { + padding: .75rem; + margin-top: 0; + margin-bottom: .75rem; + overflow-x: auto; + background-color: #f5f6fa; + border-radius: 4px; + box-shadow: none; + -webkit-overflow-scrolling: touch +} + +div.highlighter-rouge div.highlight, div.highlighter-rouge pre.highlight, div.highlighter-rouge code { + padding: 0; + margin: 0; + border: 0 +} + +figure.highlight { + padding: .75rem; + margin-top: 0; + margin-bottom: .75rem; + background-color: #f5f6fa; + border-radius: 4px; + box-shadow: none; + -webkit-overflow-scrolling: touch +} + +figure.highlight pre, figure.highlight code { + padding: 0; + margin: 0; + border: 0 +} + +.highlight .table-wrapper { + padding: 0; + margin: 0; + border: 0; + box-shadow: none +} + +.highlight .table-wrapper td, .highlight .table-wrapper pre { + font-size: 11px !important; + min-width: 0; + padding: 0; + background-color: #f5f6fa; + border: 0 +} + +@media (min-width: 31.25rem) { + .highlight .table-wrapper td, .highlight .table-wrapper pre { + font-size: 12px !important + } +} + +.highlight .table-wrapper td.gl { + padding-right: .75rem +} + +.highlight .table-wrapper pre { + margin: 0; + line-height: 2 +} + +.highlight .c { + color: #586e75 +} + +.highlight .err { + color: #93a1a1 +} + +.highlight .g { + color: #93a1a1 +} + +.highlight .k { + color: #859900 +} + +.highlight .l { + color: #93a1a1 +} + +.highlight .n { + color: #93a1a1 +} + +.highlight .o { + color: #859900 +} + +.highlight .x { + color: #cb4b16 +} + +.highlight .p { + color: #93a1a1 +} + +.highlight .cm { + color: #586e75 +} + +.highlight .cp { + color: #859900 +} + +.highlight .c1 { + color: #586e75 +} + +.highlight .cs { + color: #859900 +} + +.highlight .gd { + color: #2aa198 +} + +.highlight .ge { + font-style: italic; + color: #93a1a1 +} + +.highlight .gr { + color: #dc322f +} + +.highlight .gh { + color: #cb4b16 +} + +.highlight .gi { + color: #859900 +} + +.highlight .go { + color: #93a1a1 +} + +.highlight .gp { + color: #93a1a1 +} + +.highlight .gs { + font-weight: bold; + color: #93a1a1 +} + +.highlight .gu { + color: #cb4b16 +} + +.highlight .gt { + color: #93a1a1 +} + +.highlight .kc { + color: #cb4b16 +} + +.highlight .kd { + color: #268bd2 +} + +.highlight .kn { + color: #859900 +} + +.highlight .kp { + color: #859900 +} + +.highlight .kr { + color: #268bd2 +} + +.highlight .kt { + color: #dc322f +} + +.highlight .ld { + color: #93a1a1 +} + +.highlight .m { + color: #2aa198 +} + +.highlight .s { + color: #2aa198 +} + +.highlight .na { + color: #555 +} + +.highlight .nb { + color: #b58900 +} + +.highlight .nc { + color: #268bd2 +} + +.highlight .no { + color: #cb4b16 +} + +.highlight .nd { + color: #268bd2 +} + +.highlight .ni { + color: #cb4b16 +} + +.highlight .ne { + color: #cb4b16 +} + +.highlight .nf { + color: #268bd2 +} + +.highlight .nl { + color: #555 +} + +.highlight .nn { + color: #93a1a1 +} + +.highlight .nx { + color: #555 +} + +.highlight .py { + color: #93a1a1 +} + +.highlight .nt { + color: #268bd2 +} + +.highlight .nv { + color: #268bd2 +} + +.highlight .ow { + color: #859900 +} + +.highlight .w { + color: #93a1a1 +} + +.highlight .mf { + color: #2aa198 +} + +.highlight .mh { + color: #2aa198 +} + +.highlight .mi { + color: #2aa198 +} + +.highlight .mo { + color: #2aa198 +} + +.highlight .sb { + color: #586e75 +} + +.highlight .sc { + color: #2aa198 +} + +.highlight .sd { + color: #93a1a1 +} + +.highlight .s2 { + color: #2aa198 +} + +.highlight .se { + color: #cb4b16 +} + +.highlight .sh { + color: #93a1a1 +} + +.highlight .si { + color: #2aa198 +} + +.highlight .sx { + color: #2aa198 +} + +.highlight .sr { + color: #dc322f +} + +.highlight .s1 { + color: #2aa198 +} + +.highlight .ss { + color: #2aa198 +} + +.highlight .bp { + color: #268bd2 +} + +.highlight .vc { + color: #268bd2 +} + +.highlight .vg { + color: #268bd2 +} + +.highlight .vi { + color: #268bd2 +} + +.highlight .il { + color: #2aa198 +} + +.code-example { + padding: .75rem; + margin-bottom: .75rem; + overflow: auto; + border: 1px solid #eeebee; + border-radius: 4px +} + +.code-example + .highlighter-rouge, .code-example + figure.highlight { + position: relative; + margin-top: -1rem; + border-right: 1px solid #eeebee; + border-bottom: 1px solid #eeebee; + border-left: 1px solid #eeebee; + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.text-grey-dk-000 { + color: #959396 !important +} + +.text-grey-dk-100 { + color: #5c5962 !important +} + +.text-grey-dk-200 { + color: #44434d !important +} + +.text-grey-dk-250 { + color: #302d36 !important +} + +.text-grey-dk-300 { + color: #27262b !important +} + +.text-grey-lt-000 { + color: #f5f6fa !important +} + +.text-grey-lt-100 { + color: #eeebee !important +} + +.text-grey-lt-200 { + color: #ecebed !important +} + +.text-grey-lt-300 { + color: #e6e1e8 !important +} + +.text-blue-000 { + color: #2c84fa !important +} + +.text-blue-100 { + color: #2869e6 !important +} + +.text-blue-200 { + color: #264caf !important +} + +.text-blue-300 { + color: #183385 !important +} + +.text-green-000 { + color: #41d693 !important +} + +.text-green-100 { + color: #11b584 !important +} + +.text-green-200 { + color: #009c7b !important +} + +.text-green-300 { + color: #026e57 !important +} + +.text-purple-000 { + color: #7253ed !important +} + +.text-purple-100 { + color: #5e41d0 !important +} + +.text-purple-200 { + color: #4e26af !important +} + +.text-purple-300 { + color: #381885 !important +} + +.text-yellow-000 { + color: #ffeb82 !important +} + +.text-yellow-100 { + color: #fadf50 !important +} + +.text-yellow-200 { + color: #f7d12e !important +} + +.text-yellow-300 { + color: #e7af06 !important +} + +.text-red-000 { + color: #f77e7e !important +} + +.text-red-100 { + color: #f96e65 !important +} + +.text-red-200 { + color: #e94c4c !important +} + +.text-red-300 { + color: #dd2e2e !important +} + +.bg-grey-dk-000 { + background-color: #959396 !important +} + +.bg-grey-dk-100 { + background-color: #5c5962 !important +} + +.bg-grey-dk-200 { + background-color: #44434d !important +} + +.bg-grey-dk-250 { + background-color: #302d36 !important +} + +.bg-grey-dk-300 { + background-color: #27262b !important +} + +.bg-grey-lt-000 { + background-color: #f5f6fa !important +} + +.bg-grey-lt-100 { + background-color: #eeebee !important +} + +.bg-grey-lt-200 { + background-color: #ecebed !important +} + +.bg-grey-lt-300 { + background-color: #e6e1e8 !important +} + +.bg-blue-000 { + background-color: #2c84fa !important +} + +.bg-blue-100 { + background-color: #2869e6 !important +} + +.bg-blue-200 { + background-color: #264caf !important +} + +.bg-blue-300 { + background-color: #183385 !important +} + +.bg-green-000 { + background-color: #41d693 !important +} + +.bg-green-100 { + background-color: #11b584 !important +} + +.bg-green-200 { + background-color: #009c7b !important +} + +.bg-green-300 { + background-color: #026e57 !important +} + +.bg-purple-000 { + background-color: #7253ed !important +} + +.bg-purple-100 { + background-color: #5e41d0 !important +} + +.bg-purple-200 { + background-color: #4e26af !important +} + +.bg-purple-300 { + background-color: #381885 !important +} + +.bg-yellow-000 { + background-color: #ffeb82 !important +} + +.bg-yellow-100 { + background-color: #fadf50 !important +} + +.bg-yellow-200 { + background-color: #f7d12e !important +} + +.bg-yellow-300 { + background-color: #e7af06 !important +} + +.bg-red-000 { + background-color: #f77e7e !important +} + +.bg-red-100 { + background-color: #f96e65 !important +} + +.bg-red-200 { + background-color: #e94c4c !important +} + +.bg-red-300 { + background-color: #dd2e2e !important +} + +.d-block { + display: block !important +} + +.d-flex { + display: flex !important +} + +.d-inline { + display: inline !important +} + +.d-inline-block { + display: inline-block !important +} + +.d-none { + display: none !important +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 20rem) { + .d-xs-block { + display: block !important + } + + .d-xs-flex { + display: flex !important + } + + .d-xs-inline { + display: inline !important + } + + .d-xs-inline-block { + display: inline-block !important + } + + .d-xs-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 31.25rem) { + .d-sm-block { + display: block !important + } + + .d-sm-flex { + display: flex !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 50rem) { + .d-md-block { + display: block !important + } + + .d-md-flex { + display: flex !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 66.5rem) { + .d-lg-block { + display: block !important + } + + .d-lg-flex { + display: flex !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +@media (min-width: 87.5rem) { + .d-xl-block { + display: block !important + } + + .d-xl-flex { + display: flex !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-none { + display: none !important + } +} + +.float-left { + float: left !important +} + +.float-right { + float: right !important +} + +.flex-justify-start { + justify-content: flex-start !important +} + +.flex-justify-end { + justify-content: flex-end !important +} + +.flex-justify-between { + justify-content: space-between !important +} + +.flex-justify-around { + justify-content: space-around !important +} + +.v-align-baseline { + vertical-align: baseline !important +} + +.v-align-bottom { + vertical-align: bottom !important +} + +.v-align-middle { + vertical-align: middle !important +} + +.v-align-text-bottom { + vertical-align: text-bottom !important +} + +.v-align-text-top { + vertical-align: text-top !important +} + +.v-align-top { + vertical-align: top !important +} + +.fs-1 { + font-size: 9px !important +} + +@media (min-width: 31.25rem) { + .fs-1 { + font-size: 10px !important + } +} + +.fs-2 { + font-size: 11px !important +} + +@media (min-width: 31.25rem) { + .fs-2 { + font-size: 12px !important + } +} + +.fs-3 { + font-size: 12px !important +} + +@media (min-width: 31.25rem) { + .fs-3 { + font-size: 14px !important + } +} + +.fs-4 { + font-size: 14px !important +} + +@media (min-width: 31.25rem) { + .fs-4 { + font-size: 16px !important + } +} + +.fs-5 { + font-size: 16px !important +} + +@media (min-width: 31.25rem) { + .fs-5 { + font-size: 18px !important + } +} + +.fs-6 { + font-size: 18px !important +} + +@media (min-width: 31.25rem) { + .fs-6 { + font-size: 24px !important; + line-height: 1.25 + } +} + +.fs-7 { + font-size: 24px !important; + line-height: 1.25 +} + +@media (min-width: 31.25rem) { + .fs-7 { + font-size: 32px !important + } +} + +.fs-8 { + font-size: 32px !important; + line-height: 1.25 +} + +@media (min-width: 31.25rem) { + .fs-8 { + font-size: 36px !important + } +} + +.fs-9 { + font-size: 36px !important; + line-height: 1.25 +} + +@media (min-width: 31.25rem) { + .fs-9 { + font-size: 42px !important + } +} + +.fs-10 { + font-size: 42px !important; + line-height: 1.25 +} + +@media (min-width: 31.25rem) { + .fs-10 { + font-size: 48px !important + } +} + +.fw-300 { + font-weight: 300 !important +} + +.fw-400 { + font-weight: 400 !important +} + +.fw-500 { + font-weight: 500 !important +} + +.fw-700 { + font-weight: 700 !important +} + +.lh-0 { + line-height: 0 !important +} + +.lh-default { + line-height: 1.4 +} + +.lh-tight { + line-height: 1.25 +} + +.ls-5 { + letter-spacing: 0.05em !important +} + +.ls-10 { + letter-spacing: 0.1em !important +} + +.ls-0 { + letter-spacing: 0 !important +} + +.text-uppercase { + text-transform: uppercase !important +} + +.list-style-none { + padding: 0 !important; + margin: 0 !important; + list-style: none !important +} + +.list-style-none li::before { + display: none !important +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-0 { + margin: 0 !important +} + +.mt-0 { + margin-top: 0 !important +} + +.mr-0 { + margin-right: 0 !important +} + +.mb-0 { + margin-bottom: 0 !important +} + +.ml-0 { + margin-left: 0 !important +} + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important +} + +.mxn-0 { + margin-right: -0 !important; + margin-left: -0 !important +} + +.mx-0-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-1 { + margin: .25rem !important +} + +.mt-1 { + margin-top: .25rem !important +} + +.mr-1 { + margin-right: .25rem !important +} + +.mb-1 { + margin-bottom: .25rem !important +} + +.ml-1 { + margin-left: .25rem !important +} + +.mx-1 { + margin-right: .25rem !important; + margin-left: .25rem !important +} + +.my-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important +} + +.mxn-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important +} + +.mx-1-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-2 { + margin: .5rem !important +} + +.mt-2 { + margin-top: .5rem !important +} + +.mr-2 { + margin-right: .5rem !important +} + +.mb-2 { + margin-bottom: .5rem !important +} + +.ml-2 { + margin-left: .5rem !important +} + +.mx-2 { + margin-right: .5rem !important; + margin-left: .5rem !important +} + +.my-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important +} + +.mxn-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important +} + +.mx-2-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-3 { + margin: .75rem !important +} + +.mt-3 { + margin-top: .75rem !important +} + +.mr-3 { + margin-right: .75rem !important +} + +.mb-3 { + margin-bottom: .75rem !important +} + +.ml-3 { + margin-left: .75rem !important +} + +.mx-3 { + margin-right: .75rem !important; + margin-left: .75rem !important +} + +.my-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important +} + +.mxn-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important +} + +.mx-3-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-4 { + margin: 1rem !important +} + +.mt-4 { + margin-top: 1rem !important +} + +.mr-4 { + margin-right: 1rem !important +} + +.mb-4 { + margin-bottom: 1rem !important +} + +.ml-4 { + margin-left: 1rem !important +} + +.mx-4 { + margin-right: 1rem !important; + margin-left: 1rem !important +} + +.my-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important +} + +.mxn-4 { + margin-right: -1rem !important; + margin-left: -1rem !important +} + +.mx-4-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-5 { + margin: 1.5rem !important +} + +.mt-5 { + margin-top: 1.5rem !important +} + +.mr-5 { + margin-right: 1.5rem !important +} + +.mb-5 { + margin-bottom: 1.5rem !important +} + +.ml-5 { + margin-left: 1.5rem !important +} + +.mx-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important +} + +.my-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important +} + +.mxn-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important +} + +.mx-5-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-6 { + margin: 2rem !important +} + +.mt-6 { + margin-top: 2rem !important +} + +.mr-6 { + margin-right: 2rem !important +} + +.mb-6 { + margin-bottom: 2rem !important +} + +.ml-6 { + margin-left: 2rem !important +} + +.mx-6 { + margin-right: 2rem !important; + margin-left: 2rem !important +} + +.my-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important +} + +.mxn-6 { + margin-right: -2rem !important; + margin-left: -2rem !important +} + +.mx-6-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-7 { + margin: 2.5rem !important +} + +.mt-7 { + margin-top: 2.5rem !important +} + +.mr-7 { + margin-right: 2.5rem !important +} + +.mb-7 { + margin-bottom: 2.5rem !important +} + +.ml-7 { + margin-left: 2.5rem !important +} + +.mx-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important +} + +.my-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important +} + +.mxn-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important +} + +.mx-7-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-8 { + margin: 3rem !important +} + +.mt-8 { + margin-top: 3rem !important +} + +.mr-8 { + margin-right: 3rem !important +} + +.mb-8 { + margin-bottom: 3rem !important +} + +.ml-8 { + margin-left: 3rem !important +} + +.mx-8 { + margin-right: 3rem !important; + margin-left: 3rem !important +} + +.my-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important +} + +.mxn-8 { + margin-right: -3rem !important; + margin-left: -3rem !important +} + +.mx-8-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-9 { + margin: 3.5rem !important +} + +.mt-9 { + margin-top: 3.5rem !important +} + +.mr-9 { + margin-right: 3.5rem !important +} + +.mb-9 { + margin-bottom: 3.5rem !important +} + +.ml-9 { + margin-left: 3.5rem !important +} + +.mx-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important +} + +.my-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important +} + +.mxn-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important +} + +.mx-9-auto { + margin-right: auto !important; + margin-left: auto !important +} + +.m-10 { + margin: 4rem !important +} + +.mt-10 { + margin-top: 4rem !important +} + +.mr-10 { + margin-right: 4rem !important +} + +.mb-10 { + margin-bottom: 4rem !important +} + +.ml-10 { + margin-left: 4rem !important +} + +.mx-10 { + margin-right: 4rem !important; + margin-left: 4rem !important +} + +.my-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important +} + +.mxn-10 { + margin-right: -4rem !important; + margin-left: -4rem !important +} + +.mx-10-auto { + margin-right: auto !important; + margin-left: auto !important +} + +@media (min-width: 20rem) { + .m-xs-0 { + margin: 0 !important + } + + .mt-xs-0 { + margin-top: 0 !important + } + + .mr-xs-0 { + margin-right: 0 !important + } + + .mb-xs-0 { + margin-bottom: 0 !important + } + + .ml-xs-0 { + margin-left: 0 !important + } + + .mx-xs-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .my-xs-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .mxn-xs-0 { + margin-right: -0 !important; + margin-left: -0 !important + } +} + +@media (min-width: 20rem) { + .m-xs-1 { + margin: .25rem !important + } + + .mt-xs-1 { + margin-top: .25rem !important + } + + .mr-xs-1 { + margin-right: .25rem !important + } + + .mb-xs-1 { + margin-bottom: .25rem !important + } + + .ml-xs-1 { + margin-left: .25rem !important + } + + .mx-xs-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .my-xs-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .mxn-xs-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-2 { + margin: .5rem !important + } + + .mt-xs-2 { + margin-top: .5rem !important + } + + .mr-xs-2 { + margin-right: .5rem !important + } + + .mb-xs-2 { + margin-bottom: .5rem !important + } + + .ml-xs-2 { + margin-left: .5rem !important + } + + .mx-xs-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .my-xs-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .mxn-xs-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-3 { + margin: .75rem !important + } + + .mt-xs-3 { + margin-top: .75rem !important + } + + .mr-xs-3 { + margin-right: .75rem !important + } + + .mb-xs-3 { + margin-bottom: .75rem !important + } + + .ml-xs-3 { + margin-left: .75rem !important + } + + .mx-xs-3 { + margin-right: .75rem !important; + margin-left: .75rem !important + } + + .my-xs-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important + } + + .mxn-xs-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-4 { + margin: 1rem !important + } + + .mt-xs-4 { + margin-top: 1rem !important + } + + .mr-xs-4 { + margin-right: 1rem !important + } + + .mb-xs-4 { + margin-bottom: 1rem !important + } + + .ml-xs-4 { + margin-left: 1rem !important + } + + .mx-xs-4 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .my-xs-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .mxn-xs-4 { + margin-right: -1rem !important; + margin-left: -1rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-5 { + margin: 1.5rem !important + } + + .mt-xs-5 { + margin-top: 1.5rem !important + } + + .mr-xs-5 { + margin-right: 1.5rem !important + } + + .mb-xs-5 { + margin-bottom: 1.5rem !important + } + + .ml-xs-5 { + margin-left: 1.5rem !important + } + + .mx-xs-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .my-xs-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .mxn-xs-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-6 { + margin: 2rem !important + } + + .mt-xs-6 { + margin-top: 2rem !important + } + + .mr-xs-6 { + margin-right: 2rem !important + } + + .mb-xs-6 { + margin-bottom: 2rem !important + } + + .ml-xs-6 { + margin-left: 2rem !important + } + + .mx-xs-6 { + margin-right: 2rem !important; + margin-left: 2rem !important + } + + .my-xs-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important + } + + .mxn-xs-6 { + margin-right: -2rem !important; + margin-left: -2rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-7 { + margin: 2.5rem !important + } + + .mt-xs-7 { + margin-top: 2.5rem !important + } + + .mr-xs-7 { + margin-right: 2.5rem !important + } + + .mb-xs-7 { + margin-bottom: 2.5rem !important + } + + .ml-xs-7 { + margin-left: 2.5rem !important + } + + .mx-xs-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important + } + + .my-xs-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important + } + + .mxn-xs-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-8 { + margin: 3rem !important + } + + .mt-xs-8 { + margin-top: 3rem !important + } + + .mr-xs-8 { + margin-right: 3rem !important + } + + .mb-xs-8 { + margin-bottom: 3rem !important + } + + .ml-xs-8 { + margin-left: 3rem !important + } + + .mx-xs-8 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .my-xs-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .mxn-xs-8 { + margin-right: -3rem !important; + margin-left: -3rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-9 { + margin: 3.5rem !important + } + + .mt-xs-9 { + margin-top: 3.5rem !important + } + + .mr-xs-9 { + margin-right: 3.5rem !important + } + + .mb-xs-9 { + margin-bottom: 3.5rem !important + } + + .ml-xs-9 { + margin-left: 3.5rem !important + } + + .mx-xs-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important + } + + .my-xs-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important + } + + .mxn-xs-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important + } +} + +@media (min-width: 20rem) { + .m-xs-10 { + margin: 4rem !important + } + + .mt-xs-10 { + margin-top: 4rem !important + } + + .mr-xs-10 { + margin-right: 4rem !important + } + + .mb-xs-10 { + margin-bottom: 4rem !important + } + + .ml-xs-10 { + margin-left: 4rem !important + } + + .mx-xs-10 { + margin-right: 4rem !important; + margin-left: 4rem !important + } + + .my-xs-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important + } + + .mxn-xs-10 { + margin-right: -4rem !important; + margin-left: -4rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-0 { + margin: 0 !important + } + + .mt-sm-0 { + margin-top: 0 !important + } + + .mr-sm-0 { + margin-right: 0 !important + } + + .mb-sm-0 { + margin-bottom: 0 !important + } + + .ml-sm-0 { + margin-left: 0 !important + } + + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .mxn-sm-0 { + margin-right: -0 !important; + margin-left: -0 !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-1 { + margin: .25rem !important + } + + .mt-sm-1 { + margin-top: .25rem !important + } + + .mr-sm-1 { + margin-right: .25rem !important + } + + .mb-sm-1 { + margin-bottom: .25rem !important + } + + .ml-sm-1 { + margin-left: .25rem !important + } + + .mx-sm-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .my-sm-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .mxn-sm-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-2 { + margin: .5rem !important + } + + .mt-sm-2 { + margin-top: .5rem !important + } + + .mr-sm-2 { + margin-right: .5rem !important + } + + .mb-sm-2 { + margin-bottom: .5rem !important + } + + .ml-sm-2 { + margin-left: .5rem !important + } + + .mx-sm-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .my-sm-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .mxn-sm-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-3 { + margin: .75rem !important + } + + .mt-sm-3 { + margin-top: .75rem !important + } + + .mr-sm-3 { + margin-right: .75rem !important + } + + .mb-sm-3 { + margin-bottom: .75rem !important + } + + .ml-sm-3 { + margin-left: .75rem !important + } + + .mx-sm-3 { + margin-right: .75rem !important; + margin-left: .75rem !important + } + + .my-sm-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important + } + + .mxn-sm-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-4 { + margin: 1rem !important + } + + .mt-sm-4 { + margin-top: 1rem !important + } + + .mr-sm-4 { + margin-right: 1rem !important + } + + .mb-sm-4 { + margin-bottom: 1rem !important + } + + .ml-sm-4 { + margin-left: 1rem !important + } + + .mx-sm-4 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .my-sm-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .mxn-sm-4 { + margin-right: -1rem !important; + margin-left: -1rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-5 { + margin: 1.5rem !important + } + + .mt-sm-5 { + margin-top: 1.5rem !important + } + + .mr-sm-5 { + margin-right: 1.5rem !important + } + + .mb-sm-5 { + margin-bottom: 1.5rem !important + } + + .ml-sm-5 { + margin-left: 1.5rem !important + } + + .mx-sm-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .my-sm-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .mxn-sm-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-6 { + margin: 2rem !important + } + + .mt-sm-6 { + margin-top: 2rem !important + } + + .mr-sm-6 { + margin-right: 2rem !important + } + + .mb-sm-6 { + margin-bottom: 2rem !important + } + + .ml-sm-6 { + margin-left: 2rem !important + } + + .mx-sm-6 { + margin-right: 2rem !important; + margin-left: 2rem !important + } + + .my-sm-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important + } + + .mxn-sm-6 { + margin-right: -2rem !important; + margin-left: -2rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-7 { + margin: 2.5rem !important + } + + .mt-sm-7 { + margin-top: 2.5rem !important + } + + .mr-sm-7 { + margin-right: 2.5rem !important + } + + .mb-sm-7 { + margin-bottom: 2.5rem !important + } + + .ml-sm-7 { + margin-left: 2.5rem !important + } + + .mx-sm-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important + } + + .my-sm-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important + } + + .mxn-sm-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-8 { + margin: 3rem !important + } + + .mt-sm-8 { + margin-top: 3rem !important + } + + .mr-sm-8 { + margin-right: 3rem !important + } + + .mb-sm-8 { + margin-bottom: 3rem !important + } + + .ml-sm-8 { + margin-left: 3rem !important + } + + .mx-sm-8 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .my-sm-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .mxn-sm-8 { + margin-right: -3rem !important; + margin-left: -3rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-9 { + margin: 3.5rem !important + } + + .mt-sm-9 { + margin-top: 3.5rem !important + } + + .mr-sm-9 { + margin-right: 3.5rem !important + } + + .mb-sm-9 { + margin-bottom: 3.5rem !important + } + + .ml-sm-9 { + margin-left: 3.5rem !important + } + + .mx-sm-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important + } + + .my-sm-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important + } + + .mxn-sm-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important + } +} + +@media (min-width: 31.25rem) { + .m-sm-10 { + margin: 4rem !important + } + + .mt-sm-10 { + margin-top: 4rem !important + } + + .mr-sm-10 { + margin-right: 4rem !important + } + + .mb-sm-10 { + margin-bottom: 4rem !important + } + + .ml-sm-10 { + margin-left: 4rem !important + } + + .mx-sm-10 { + margin-right: 4rem !important; + margin-left: 4rem !important + } + + .my-sm-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important + } + + .mxn-sm-10 { + margin-right: -4rem !important; + margin-left: -4rem !important + } +} + +@media (min-width: 50rem) { + .m-md-0 { + margin: 0 !important + } + + .mt-md-0 { + margin-top: 0 !important + } + + .mr-md-0 { + margin-right: 0 !important + } + + .mb-md-0 { + margin-bottom: 0 !important + } + + .ml-md-0 { + margin-left: 0 !important + } + + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .mxn-md-0 { + margin-right: -0 !important; + margin-left: -0 !important + } +} + +@media (min-width: 50rem) { + .m-md-1 { + margin: .25rem !important + } + + .mt-md-1 { + margin-top: .25rem !important + } + + .mr-md-1 { + margin-right: .25rem !important + } + + .mb-md-1 { + margin-bottom: .25rem !important + } + + .ml-md-1 { + margin-left: .25rem !important + } + + .mx-md-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .my-md-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .mxn-md-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important + } +} + +@media (min-width: 50rem) { + .m-md-2 { + margin: .5rem !important + } + + .mt-md-2 { + margin-top: .5rem !important + } + + .mr-md-2 { + margin-right: .5rem !important + } + + .mb-md-2 { + margin-bottom: .5rem !important + } + + .ml-md-2 { + margin-left: .5rem !important + } + + .mx-md-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .my-md-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .mxn-md-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important + } +} + +@media (min-width: 50rem) { + .m-md-3 { + margin: .75rem !important + } + + .mt-md-3 { + margin-top: .75rem !important + } + + .mr-md-3 { + margin-right: .75rem !important + } + + .mb-md-3 { + margin-bottom: .75rem !important + } + + .ml-md-3 { + margin-left: .75rem !important + } + + .mx-md-3 { + margin-right: .75rem !important; + margin-left: .75rem !important + } + + .my-md-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important + } + + .mxn-md-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important + } +} + +@media (min-width: 50rem) { + .m-md-4 { + margin: 1rem !important + } + + .mt-md-4 { + margin-top: 1rem !important + } + + .mr-md-4 { + margin-right: 1rem !important + } + + .mb-md-4 { + margin-bottom: 1rem !important + } + + .ml-md-4 { + margin-left: 1rem !important + } + + .mx-md-4 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .my-md-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .mxn-md-4 { + margin-right: -1rem !important; + margin-left: -1rem !important + } +} + +@media (min-width: 50rem) { + .m-md-5 { + margin: 1.5rem !important + } + + .mt-md-5 { + margin-top: 1.5rem !important + } + + .mr-md-5 { + margin-right: 1.5rem !important + } + + .mb-md-5 { + margin-bottom: 1.5rem !important + } + + .ml-md-5 { + margin-left: 1.5rem !important + } + + .mx-md-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .my-md-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .mxn-md-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important + } +} + +@media (min-width: 50rem) { + .m-md-6 { + margin: 2rem !important + } + + .mt-md-6 { + margin-top: 2rem !important + } + + .mr-md-6 { + margin-right: 2rem !important + } + + .mb-md-6 { + margin-bottom: 2rem !important + } + + .ml-md-6 { + margin-left: 2rem !important + } + + .mx-md-6 { + margin-right: 2rem !important; + margin-left: 2rem !important + } + + .my-md-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important + } + + .mxn-md-6 { + margin-right: -2rem !important; + margin-left: -2rem !important + } +} + +@media (min-width: 50rem) { + .m-md-7 { + margin: 2.5rem !important + } + + .mt-md-7 { + margin-top: 2.5rem !important + } + + .mr-md-7 { + margin-right: 2.5rem !important + } + + .mb-md-7 { + margin-bottom: 2.5rem !important + } + + .ml-md-7 { + margin-left: 2.5rem !important + } + + .mx-md-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important + } + + .my-md-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important + } + + .mxn-md-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important + } +} + +@media (min-width: 50rem) { + .m-md-8 { + margin: 3rem !important + } + + .mt-md-8 { + margin-top: 3rem !important + } + + .mr-md-8 { + margin-right: 3rem !important + } + + .mb-md-8 { + margin-bottom: 3rem !important + } + + .ml-md-8 { + margin-left: 3rem !important + } + + .mx-md-8 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .my-md-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .mxn-md-8 { + margin-right: -3rem !important; + margin-left: -3rem !important + } +} + +@media (min-width: 50rem) { + .m-md-9 { + margin: 3.5rem !important + } + + .mt-md-9 { + margin-top: 3.5rem !important + } + + .mr-md-9 { + margin-right: 3.5rem !important + } + + .mb-md-9 { + margin-bottom: 3.5rem !important + } + + .ml-md-9 { + margin-left: 3.5rem !important + } + + .mx-md-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important + } + + .my-md-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important + } + + .mxn-md-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important + } +} + +@media (min-width: 50rem) { + .m-md-10 { + margin: 4rem !important + } + + .mt-md-10 { + margin-top: 4rem !important + } + + .mr-md-10 { + margin-right: 4rem !important + } + + .mb-md-10 { + margin-bottom: 4rem !important + } + + .ml-md-10 { + margin-left: 4rem !important + } + + .mx-md-10 { + margin-right: 4rem !important; + margin-left: 4rem !important + } + + .my-md-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important + } + + .mxn-md-10 { + margin-right: -4rem !important; + margin-left: -4rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-0 { + margin: 0 !important + } + + .mt-lg-0 { + margin-top: 0 !important + } + + .mr-lg-0 { + margin-right: 0 !important + } + + .mb-lg-0 { + margin-bottom: 0 !important + } + + .ml-lg-0 { + margin-left: 0 !important + } + + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .mxn-lg-0 { + margin-right: -0 !important; + margin-left: -0 !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-1 { + margin: .25rem !important + } + + .mt-lg-1 { + margin-top: .25rem !important + } + + .mr-lg-1 { + margin-right: .25rem !important + } + + .mb-lg-1 { + margin-bottom: .25rem !important + } + + .ml-lg-1 { + margin-left: .25rem !important + } + + .mx-lg-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .my-lg-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .mxn-lg-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-2 { + margin: .5rem !important + } + + .mt-lg-2 { + margin-top: .5rem !important + } + + .mr-lg-2 { + margin-right: .5rem !important + } + + .mb-lg-2 { + margin-bottom: .5rem !important + } + + .ml-lg-2 { + margin-left: .5rem !important + } + + .mx-lg-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .my-lg-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .mxn-lg-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-3 { + margin: .75rem !important + } + + .mt-lg-3 { + margin-top: .75rem !important + } + + .mr-lg-3 { + margin-right: .75rem !important + } + + .mb-lg-3 { + margin-bottom: .75rem !important + } + + .ml-lg-3 { + margin-left: .75rem !important + } + + .mx-lg-3 { + margin-right: .75rem !important; + margin-left: .75rem !important + } + + .my-lg-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important + } + + .mxn-lg-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-4 { + margin: 1rem !important + } + + .mt-lg-4 { + margin-top: 1rem !important + } + + .mr-lg-4 { + margin-right: 1rem !important + } + + .mb-lg-4 { + margin-bottom: 1rem !important + } + + .ml-lg-4 { + margin-left: 1rem !important + } + + .mx-lg-4 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .my-lg-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .mxn-lg-4 { + margin-right: -1rem !important; + margin-left: -1rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-5 { + margin: 1.5rem !important + } + + .mt-lg-5 { + margin-top: 1.5rem !important + } + + .mr-lg-5 { + margin-right: 1.5rem !important + } + + .mb-lg-5 { + margin-bottom: 1.5rem !important + } + + .ml-lg-5 { + margin-left: 1.5rem !important + } + + .mx-lg-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .my-lg-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .mxn-lg-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-6 { + margin: 2rem !important + } + + .mt-lg-6 { + margin-top: 2rem !important + } + + .mr-lg-6 { + margin-right: 2rem !important + } + + .mb-lg-6 { + margin-bottom: 2rem !important + } + + .ml-lg-6 { + margin-left: 2rem !important + } + + .mx-lg-6 { + margin-right: 2rem !important; + margin-left: 2rem !important + } + + .my-lg-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important + } + + .mxn-lg-6 { + margin-right: -2rem !important; + margin-left: -2rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-7 { + margin: 2.5rem !important + } + + .mt-lg-7 { + margin-top: 2.5rem !important + } + + .mr-lg-7 { + margin-right: 2.5rem !important + } + + .mb-lg-7 { + margin-bottom: 2.5rem !important + } + + .ml-lg-7 { + margin-left: 2.5rem !important + } + + .mx-lg-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important + } + + .my-lg-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important + } + + .mxn-lg-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-8 { + margin: 3rem !important + } + + .mt-lg-8 { + margin-top: 3rem !important + } + + .mr-lg-8 { + margin-right: 3rem !important + } + + .mb-lg-8 { + margin-bottom: 3rem !important + } + + .ml-lg-8 { + margin-left: 3rem !important + } + + .mx-lg-8 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .my-lg-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .mxn-lg-8 { + margin-right: -3rem !important; + margin-left: -3rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-9 { + margin: 3.5rem !important + } + + .mt-lg-9 { + margin-top: 3.5rem !important + } + + .mr-lg-9 { + margin-right: 3.5rem !important + } + + .mb-lg-9 { + margin-bottom: 3.5rem !important + } + + .ml-lg-9 { + margin-left: 3.5rem !important + } + + .mx-lg-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important + } + + .my-lg-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important + } + + .mxn-lg-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important + } +} + +@media (min-width: 66.5rem) { + .m-lg-10 { + margin: 4rem !important + } + + .mt-lg-10 { + margin-top: 4rem !important + } + + .mr-lg-10 { + margin-right: 4rem !important + } + + .mb-lg-10 { + margin-bottom: 4rem !important + } + + .ml-lg-10 { + margin-left: 4rem !important + } + + .mx-lg-10 { + margin-right: 4rem !important; + margin-left: 4rem !important + } + + .my-lg-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important + } + + .mxn-lg-10 { + margin-right: -4rem !important; + margin-left: -4rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-0 { + margin: 0 !important + } + + .mt-xl-0 { + margin-top: 0 !important + } + + .mr-xl-0 { + margin-right: 0 !important + } + + .mb-xl-0 { + margin-bottom: 0 !important + } + + .ml-xl-0 { + margin-left: 0 !important + } + + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important + } + + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important + } + + .mxn-xl-0 { + margin-right: -0 !important; + margin-left: -0 !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-1 { + margin: .25rem !important + } + + .mt-xl-1 { + margin-top: .25rem !important + } + + .mr-xl-1 { + margin-right: .25rem !important + } + + .mb-xl-1 { + margin-bottom: .25rem !important + } + + .ml-xl-1 { + margin-left: .25rem !important + } + + .mx-xl-1 { + margin-right: .25rem !important; + margin-left: .25rem !important + } + + .my-xl-1 { + margin-top: .25rem !important; + margin-bottom: .25rem !important + } + + .mxn-xl-1 { + margin-right: -.25rem !important; + margin-left: -.25rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-2 { + margin: .5rem !important + } + + .mt-xl-2 { + margin-top: .5rem !important + } + + .mr-xl-2 { + margin-right: .5rem !important + } + + .mb-xl-2 { + margin-bottom: .5rem !important + } + + .ml-xl-2 { + margin-left: .5rem !important + } + + .mx-xl-2 { + margin-right: .5rem !important; + margin-left: .5rem !important + } + + .my-xl-2 { + margin-top: .5rem !important; + margin-bottom: .5rem !important + } + + .mxn-xl-2 { + margin-right: -.5rem !important; + margin-left: -.5rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-3 { + margin: .75rem !important + } + + .mt-xl-3 { + margin-top: .75rem !important + } + + .mr-xl-3 { + margin-right: .75rem !important + } + + .mb-xl-3 { + margin-bottom: .75rem !important + } + + .ml-xl-3 { + margin-left: .75rem !important + } + + .mx-xl-3 { + margin-right: .75rem !important; + margin-left: .75rem !important + } + + .my-xl-3 { + margin-top: .75rem !important; + margin-bottom: .75rem !important + } + + .mxn-xl-3 { + margin-right: -.75rem !important; + margin-left: -.75rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-4 { + margin: 1rem !important + } + + .mt-xl-4 { + margin-top: 1rem !important + } + + .mr-xl-4 { + margin-right: 1rem !important + } + + .mb-xl-4 { + margin-bottom: 1rem !important + } + + .ml-xl-4 { + margin-left: 1rem !important + } + + .mx-xl-4 { + margin-right: 1rem !important; + margin-left: 1rem !important + } + + .my-xl-4 { + margin-top: 1rem !important; + margin-bottom: 1rem !important + } + + .mxn-xl-4 { + margin-right: -1rem !important; + margin-left: -1rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-5 { + margin: 1.5rem !important + } + + .mt-xl-5 { + margin-top: 1.5rem !important + } + + .mr-xl-5 { + margin-right: 1.5rem !important + } + + .mb-xl-5 { + margin-bottom: 1.5rem !important + } + + .ml-xl-5 { + margin-left: 1.5rem !important + } + + .mx-xl-5 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important + } + + .my-xl-5 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important + } + + .mxn-xl-5 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-6 { + margin: 2rem !important + } + + .mt-xl-6 { + margin-top: 2rem !important + } + + .mr-xl-6 { + margin-right: 2rem !important + } + + .mb-xl-6 { + margin-bottom: 2rem !important + } + + .ml-xl-6 { + margin-left: 2rem !important + } + + .mx-xl-6 { + margin-right: 2rem !important; + margin-left: 2rem !important + } + + .my-xl-6 { + margin-top: 2rem !important; + margin-bottom: 2rem !important + } + + .mxn-xl-6 { + margin-right: -2rem !important; + margin-left: -2rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-7 { + margin: 2.5rem !important + } + + .mt-xl-7 { + margin-top: 2.5rem !important + } + + .mr-xl-7 { + margin-right: 2.5rem !important + } + + .mb-xl-7 { + margin-bottom: 2.5rem !important + } + + .ml-xl-7 { + margin-left: 2.5rem !important + } + + .mx-xl-7 { + margin-right: 2.5rem !important; + margin-left: 2.5rem !important + } + + .my-xl-7 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important + } + + .mxn-xl-7 { + margin-right: -2.5rem !important; + margin-left: -2.5rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-8 { + margin: 3rem !important + } + + .mt-xl-8 { + margin-top: 3rem !important + } + + .mr-xl-8 { + margin-right: 3rem !important + } + + .mb-xl-8 { + margin-bottom: 3rem !important + } + + .ml-xl-8 { + margin-left: 3rem !important + } + + .mx-xl-8 { + margin-right: 3rem !important; + margin-left: 3rem !important + } + + .my-xl-8 { + margin-top: 3rem !important; + margin-bottom: 3rem !important + } + + .mxn-xl-8 { + margin-right: -3rem !important; + margin-left: -3rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-9 { + margin: 3.5rem !important + } + + .mt-xl-9 { + margin-top: 3.5rem !important + } + + .mr-xl-9 { + margin-right: 3.5rem !important + } + + .mb-xl-9 { + margin-bottom: 3.5rem !important + } + + .ml-xl-9 { + margin-left: 3.5rem !important + } + + .mx-xl-9 { + margin-right: 3.5rem !important; + margin-left: 3.5rem !important + } + + .my-xl-9 { + margin-top: 3.5rem !important; + margin-bottom: 3.5rem !important + } + + .mxn-xl-9 { + margin-right: -3.5rem !important; + margin-left: -3.5rem !important + } +} + +@media (min-width: 87.5rem) { + .m-xl-10 { + margin: 4rem !important + } + + .mt-xl-10 { + margin-top: 4rem !important + } + + .mr-xl-10 { + margin-right: 4rem !important + } + + .mb-xl-10 { + margin-bottom: 4rem !important + } + + .ml-xl-10 { + margin-left: 4rem !important + } + + .mx-xl-10 { + margin-right: 4rem !important; + margin-left: 4rem !important + } + + .my-xl-10 { + margin-top: 4rem !important; + margin-bottom: 4rem !important + } + + .mxn-xl-10 { + margin-right: -4rem !important; + margin-left: -4rem !important + } +} + +.p-0 { + padding: 0 !important +} + +.pt-0 { + padding-top: 0 !important +} + +.pr-0 { + padding-right: 0 !important +} + +.pb-0 { + padding-bottom: 0 !important +} + +.pl-0 { + padding-left: 0 !important +} + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important +} + +.p-1 { + padding: .25rem !important +} + +.pt-1 { + padding-top: .25rem !important +} + +.pr-1 { + padding-right: .25rem !important +} + +.pb-1 { + padding-bottom: .25rem !important +} + +.pl-1 { + padding-left: .25rem !important +} + +.px-1 { + padding-right: .25rem !important; + padding-left: .25rem !important +} + +.py-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important +} + +.p-2 { + padding: .5rem !important +} + +.pt-2 { + padding-top: .5rem !important +} + +.pr-2 { + padding-right: .5rem !important +} + +.pb-2 { + padding-bottom: .5rem !important +} + +.pl-2 { + padding-left: .5rem !important +} + +.px-2 { + padding-right: .5rem !important; + padding-left: .5rem !important +} + +.py-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important +} + +.p-3 { + padding: .75rem !important +} + +.pt-3 { + padding-top: .75rem !important +} + +.pr-3 { + padding-right: .75rem !important +} + +.pb-3 { + padding-bottom: .75rem !important +} + +.pl-3 { + padding-left: .75rem !important +} + +.px-3 { + padding-right: .75rem !important; + padding-left: .75rem !important +} + +.py-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important +} + +.p-4 { + padding: 1rem !important +} + +.pt-4 { + padding-top: 1rem !important +} + +.pr-4 { + padding-right: 1rem !important +} + +.pb-4 { + padding-bottom: 1rem !important +} + +.pl-4 { + padding-left: 1rem !important +} + +.px-4 { + padding-right: 1rem !important; + padding-left: 1rem !important +} + +.py-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important +} + +.p-5 { + padding: 1.5rem !important +} + +.pt-5 { + padding-top: 1.5rem !important +} + +.pr-5 { + padding-right: 1.5rem !important +} + +.pb-5 { + padding-bottom: 1.5rem !important +} + +.pl-5 { + padding-left: 1.5rem !important +} + +.px-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important +} + +.py-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important +} + +.p-6 { + padding: 2rem !important +} + +.pt-6 { + padding-top: 2rem !important +} + +.pr-6 { + padding-right: 2rem !important +} + +.pb-6 { + padding-bottom: 2rem !important +} + +.pl-6 { + padding-left: 2rem !important +} + +.px-6 { + padding-right: 2rem !important; + padding-left: 2rem !important +} + +.py-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important +} + +.p-7 { + padding: 2.5rem !important +} + +.pt-7 { + padding-top: 2.5rem !important +} + +.pr-7 { + padding-right: 2.5rem !important +} + +.pb-7 { + padding-bottom: 2.5rem !important +} + +.pl-7 { + padding-left: 2.5rem !important +} + +.px-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important +} + +.py-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important +} + +.p-8 { + padding: 3rem !important +} + +.pt-8 { + padding-top: 3rem !important +} + +.pr-8 { + padding-right: 3rem !important +} + +.pb-8 { + padding-bottom: 3rem !important +} + +.pl-8 { + padding-left: 3rem !important +} + +.px-8 { + padding-right: 3rem !important; + padding-left: 3rem !important +} + +.py-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important +} + +.p-9 { + padding: 3.5rem !important +} + +.pt-9 { + padding-top: 3.5rem !important +} + +.pr-9 { + padding-right: 3.5rem !important +} + +.pb-9 { + padding-bottom: 3.5rem !important +} + +.pl-9 { + padding-left: 3.5rem !important +} + +.px-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important +} + +.py-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important +} + +.p-10 { + padding: 4rem !important +} + +.pt-10 { + padding-top: 4rem !important +} + +.pr-10 { + padding-right: 4rem !important +} + +.pb-10 { + padding-bottom: 4rem !important +} + +.pl-10 { + padding-left: 4rem !important +} + +.px-10 { + padding-right: 4rem !important; + padding-left: 4rem !important +} + +.py-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important +} + +@media (min-width: 20rem) { + .p-xs-0 { + padding: 0 !important + } + + .pt-xs-0 { + padding-top: 0 !important + } + + .pr-xs-0 { + padding-right: 0 !important + } + + .pb-xs-0 { + padding-bottom: 0 !important + } + + .pl-xs-0 { + padding-left: 0 !important + } + + .px-xs-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .py-xs-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .p-xs-1 { + padding: .25rem !important + } + + .pt-xs-1 { + padding-top: .25rem !important + } + + .pr-xs-1 { + padding-right: .25rem !important + } + + .pb-xs-1 { + padding-bottom: .25rem !important + } + + .pl-xs-1 { + padding-left: .25rem !important + } + + .px-xs-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .py-xs-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .p-xs-2 { + padding: .5rem !important + } + + .pt-xs-2 { + padding-top: .5rem !important + } + + .pr-xs-2 { + padding-right: .5rem !important + } + + .pb-xs-2 { + padding-bottom: .5rem !important + } + + .pl-xs-2 { + padding-left: .5rem !important + } + + .px-xs-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .py-xs-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .p-xs-3 { + padding: .75rem !important + } + + .pt-xs-3 { + padding-top: .75rem !important + } + + .pr-xs-3 { + padding-right: .75rem !important + } + + .pb-xs-3 { + padding-bottom: .75rem !important + } + + .pl-xs-3 { + padding-left: .75rem !important + } + + .px-xs-3 { + padding-right: .75rem !important; + padding-left: .75rem !important + } + + .py-xs-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important + } + + .p-xs-4 { + padding: 1rem !important + } + + .pt-xs-4 { + padding-top: 1rem !important + } + + .pr-xs-4 { + padding-right: 1rem !important + } + + .pb-xs-4 { + padding-bottom: 1rem !important + } + + .pl-xs-4 { + padding-left: 1rem !important + } + + .px-xs-4 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .py-xs-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .p-xs-5 { + padding: 1.5rem !important + } + + .pt-xs-5 { + padding-top: 1.5rem !important + } + + .pr-xs-5 { + padding-right: 1.5rem !important + } + + .pb-xs-5 { + padding-bottom: 1.5rem !important + } + + .pl-xs-5 { + padding-left: 1.5rem !important + } + + .px-xs-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .py-xs-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .p-xs-6 { + padding: 2rem !important + } + + .pt-xs-6 { + padding-top: 2rem !important + } + + .pr-xs-6 { + padding-right: 2rem !important + } + + .pb-xs-6 { + padding-bottom: 2rem !important + } + + .pl-xs-6 { + padding-left: 2rem !important + } + + .px-xs-6 { + padding-right: 2rem !important; + padding-left: 2rem !important + } + + .py-xs-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important + } + + .p-xs-7 { + padding: 2.5rem !important + } + + .pt-xs-7 { + padding-top: 2.5rem !important + } + + .pr-xs-7 { + padding-right: 2.5rem !important + } + + .pb-xs-7 { + padding-bottom: 2.5rem !important + } + + .pl-xs-7 { + padding-left: 2.5rem !important + } + + .px-xs-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important + } + + .py-xs-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important + } + + .p-xs-8 { + padding: 3rem !important + } + + .pt-xs-8 { + padding-top: 3rem !important + } + + .pr-xs-8 { + padding-right: 3rem !important + } + + .pb-xs-8 { + padding-bottom: 3rem !important + } + + .pl-xs-8 { + padding-left: 3rem !important + } + + .px-xs-8 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-xs-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .p-xs-9 { + padding: 3.5rem !important + } + + .pt-xs-9 { + padding-top: 3.5rem !important + } + + .pr-xs-9 { + padding-right: 3.5rem !important + } + + .pb-xs-9 { + padding-bottom: 3.5rem !important + } + + .pl-xs-9 { + padding-left: 3.5rem !important + } + + .px-xs-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important + } + + .py-xs-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important + } + + .p-xs-10 { + padding: 4rem !important + } + + .pt-xs-10 { + padding-top: 4rem !important + } + + .pr-xs-10 { + padding-right: 4rem !important + } + + .pb-xs-10 { + padding-bottom: 4rem !important + } + + .pl-xs-10 { + padding-left: 4rem !important + } + + .px-xs-10 { + padding-right: 4rem !important; + padding-left: 4rem !important + } + + .py-xs-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important + } +} + +@media (min-width: 31.25rem) { + .p-sm-0 { + padding: 0 !important + } + + .pt-sm-0 { + padding-top: 0 !important + } + + .pr-sm-0 { + padding-right: 0 !important + } + + .pb-sm-0 { + padding-bottom: 0 !important + } + + .pl-sm-0 { + padding-left: 0 !important + } + + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .p-sm-1 { + padding: .25rem !important + } + + .pt-sm-1 { + padding-top: .25rem !important + } + + .pr-sm-1 { + padding-right: .25rem !important + } + + .pb-sm-1 { + padding-bottom: .25rem !important + } + + .pl-sm-1 { + padding-left: .25rem !important + } + + .px-sm-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .py-sm-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .p-sm-2 { + padding: .5rem !important + } + + .pt-sm-2 { + padding-top: .5rem !important + } + + .pr-sm-2 { + padding-right: .5rem !important + } + + .pb-sm-2 { + padding-bottom: .5rem !important + } + + .pl-sm-2 { + padding-left: .5rem !important + } + + .px-sm-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .py-sm-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .p-sm-3 { + padding: .75rem !important + } + + .pt-sm-3 { + padding-top: .75rem !important + } + + .pr-sm-3 { + padding-right: .75rem !important + } + + .pb-sm-3 { + padding-bottom: .75rem !important + } + + .pl-sm-3 { + padding-left: .75rem !important + } + + .px-sm-3 { + padding-right: .75rem !important; + padding-left: .75rem !important + } + + .py-sm-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important + } + + .p-sm-4 { + padding: 1rem !important + } + + .pt-sm-4 { + padding-top: 1rem !important + } + + .pr-sm-4 { + padding-right: 1rem !important + } + + .pb-sm-4 { + padding-bottom: 1rem !important + } + + .pl-sm-4 { + padding-left: 1rem !important + } + + .px-sm-4 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .py-sm-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .p-sm-5 { + padding: 1.5rem !important + } + + .pt-sm-5 { + padding-top: 1.5rem !important + } + + .pr-sm-5 { + padding-right: 1.5rem !important + } + + .pb-sm-5 { + padding-bottom: 1.5rem !important + } + + .pl-sm-5 { + padding-left: 1.5rem !important + } + + .px-sm-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .py-sm-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .p-sm-6 { + padding: 2rem !important + } + + .pt-sm-6 { + padding-top: 2rem !important + } + + .pr-sm-6 { + padding-right: 2rem !important + } + + .pb-sm-6 { + padding-bottom: 2rem !important + } + + .pl-sm-6 { + padding-left: 2rem !important + } + + .px-sm-6 { + padding-right: 2rem !important; + padding-left: 2rem !important + } + + .py-sm-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important + } + + .p-sm-7 { + padding: 2.5rem !important + } + + .pt-sm-7 { + padding-top: 2.5rem !important + } + + .pr-sm-7 { + padding-right: 2.5rem !important + } + + .pb-sm-7 { + padding-bottom: 2.5rem !important + } + + .pl-sm-7 { + padding-left: 2.5rem !important + } + + .px-sm-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important + } + + .py-sm-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important + } + + .p-sm-8 { + padding: 3rem !important + } + + .pt-sm-8 { + padding-top: 3rem !important + } + + .pr-sm-8 { + padding-right: 3rem !important + } + + .pb-sm-8 { + padding-bottom: 3rem !important + } + + .pl-sm-8 { + padding-left: 3rem !important + } + + .px-sm-8 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-sm-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .p-sm-9 { + padding: 3.5rem !important + } + + .pt-sm-9 { + padding-top: 3.5rem !important + } + + .pr-sm-9 { + padding-right: 3.5rem !important + } + + .pb-sm-9 { + padding-bottom: 3.5rem !important + } + + .pl-sm-9 { + padding-left: 3.5rem !important + } + + .px-sm-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important + } + + .py-sm-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important + } + + .p-sm-10 { + padding: 4rem !important + } + + .pt-sm-10 { + padding-top: 4rem !important + } + + .pr-sm-10 { + padding-right: 4rem !important + } + + .pb-sm-10 { + padding-bottom: 4rem !important + } + + .pl-sm-10 { + padding-left: 4rem !important + } + + .px-sm-10 { + padding-right: 4rem !important; + padding-left: 4rem !important + } + + .py-sm-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important + } +} + +@media (min-width: 50rem) { + .p-md-0 { + padding: 0 !important + } + + .pt-md-0 { + padding-top: 0 !important + } + + .pr-md-0 { + padding-right: 0 !important + } + + .pb-md-0 { + padding-bottom: 0 !important + } + + .pl-md-0 { + padding-left: 0 !important + } + + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .p-md-1 { + padding: .25rem !important + } + + .pt-md-1 { + padding-top: .25rem !important + } + + .pr-md-1 { + padding-right: .25rem !important + } + + .pb-md-1 { + padding-bottom: .25rem !important + } + + .pl-md-1 { + padding-left: .25rem !important + } + + .px-md-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .py-md-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .p-md-2 { + padding: .5rem !important + } + + .pt-md-2 { + padding-top: .5rem !important + } + + .pr-md-2 { + padding-right: .5rem !important + } + + .pb-md-2 { + padding-bottom: .5rem !important + } + + .pl-md-2 { + padding-left: .5rem !important + } + + .px-md-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .py-md-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .p-md-3 { + padding: .75rem !important + } + + .pt-md-3 { + padding-top: .75rem !important + } + + .pr-md-3 { + padding-right: .75rem !important + } + + .pb-md-3 { + padding-bottom: .75rem !important + } + + .pl-md-3 { + padding-left: .75rem !important + } + + .px-md-3 { + padding-right: .75rem !important; + padding-left: .75rem !important + } + + .py-md-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important + } + + .p-md-4 { + padding: 1rem !important + } + + .pt-md-4 { + padding-top: 1rem !important + } + + .pr-md-4 { + padding-right: 1rem !important + } + + .pb-md-4 { + padding-bottom: 1rem !important + } + + .pl-md-4 { + padding-left: 1rem !important + } + + .px-md-4 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .py-md-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .p-md-5 { + padding: 1.5rem !important + } + + .pt-md-5 { + padding-top: 1.5rem !important + } + + .pr-md-5 { + padding-right: 1.5rem !important + } + + .pb-md-5 { + padding-bottom: 1.5rem !important + } + + .pl-md-5 { + padding-left: 1.5rem !important + } + + .px-md-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .py-md-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .p-md-6 { + padding: 2rem !important + } + + .pt-md-6 { + padding-top: 2rem !important + } + + .pr-md-6 { + padding-right: 2rem !important + } + + .pb-md-6 { + padding-bottom: 2rem !important + } + + .pl-md-6 { + padding-left: 2rem !important + } + + .px-md-6 { + padding-right: 2rem !important; + padding-left: 2rem !important + } + + .py-md-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important + } + + .p-md-7 { + padding: 2.5rem !important + } + + .pt-md-7 { + padding-top: 2.5rem !important + } + + .pr-md-7 { + padding-right: 2.5rem !important + } + + .pb-md-7 { + padding-bottom: 2.5rem !important + } + + .pl-md-7 { + padding-left: 2.5rem !important + } + + .px-md-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important + } + + .py-md-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important + } + + .p-md-8 { + padding: 3rem !important + } + + .pt-md-8 { + padding-top: 3rem !important + } + + .pr-md-8 { + padding-right: 3rem !important + } + + .pb-md-8 { + padding-bottom: 3rem !important + } + + .pl-md-8 { + padding-left: 3rem !important + } + + .px-md-8 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-md-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .p-md-9 { + padding: 3.5rem !important + } + + .pt-md-9 { + padding-top: 3.5rem !important + } + + .pr-md-9 { + padding-right: 3.5rem !important + } + + .pb-md-9 { + padding-bottom: 3.5rem !important + } + + .pl-md-9 { + padding-left: 3.5rem !important + } + + .px-md-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important + } + + .py-md-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important + } + + .p-md-10 { + padding: 4rem !important + } + + .pt-md-10 { + padding-top: 4rem !important + } + + .pr-md-10 { + padding-right: 4rem !important + } + + .pb-md-10 { + padding-bottom: 4rem !important + } + + .pl-md-10 { + padding-left: 4rem !important + } + + .px-md-10 { + padding-right: 4rem !important; + padding-left: 4rem !important + } + + .py-md-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important + } +} + +@media (min-width: 66.5rem) { + .p-lg-0 { + padding: 0 !important + } + + .pt-lg-0 { + padding-top: 0 !important + } + + .pr-lg-0 { + padding-right: 0 !important + } + + .pb-lg-0 { + padding-bottom: 0 !important + } + + .pl-lg-0 { + padding-left: 0 !important + } + + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .p-lg-1 { + padding: .25rem !important + } + + .pt-lg-1 { + padding-top: .25rem !important + } + + .pr-lg-1 { + padding-right: .25rem !important + } + + .pb-lg-1 { + padding-bottom: .25rem !important + } + + .pl-lg-1 { + padding-left: .25rem !important + } + + .px-lg-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .py-lg-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .p-lg-2 { + padding: .5rem !important + } + + .pt-lg-2 { + padding-top: .5rem !important + } + + .pr-lg-2 { + padding-right: .5rem !important + } + + .pb-lg-2 { + padding-bottom: .5rem !important + } + + .pl-lg-2 { + padding-left: .5rem !important + } + + .px-lg-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .py-lg-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .p-lg-3 { + padding: .75rem !important + } + + .pt-lg-3 { + padding-top: .75rem !important + } + + .pr-lg-3 { + padding-right: .75rem !important + } + + .pb-lg-3 { + padding-bottom: .75rem !important + } + + .pl-lg-3 { + padding-left: .75rem !important + } + + .px-lg-3 { + padding-right: .75rem !important; + padding-left: .75rem !important + } + + .py-lg-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important + } + + .p-lg-4 { + padding: 1rem !important + } + + .pt-lg-4 { + padding-top: 1rem !important + } + + .pr-lg-4 { + padding-right: 1rem !important + } + + .pb-lg-4 { + padding-bottom: 1rem !important + } + + .pl-lg-4 { + padding-left: 1rem !important + } + + .px-lg-4 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .py-lg-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .p-lg-5 { + padding: 1.5rem !important + } + + .pt-lg-5 { + padding-top: 1.5rem !important + } + + .pr-lg-5 { + padding-right: 1.5rem !important + } + + .pb-lg-5 { + padding-bottom: 1.5rem !important + } + + .pl-lg-5 { + padding-left: 1.5rem !important + } + + .px-lg-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .py-lg-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .p-lg-6 { + padding: 2rem !important + } + + .pt-lg-6 { + padding-top: 2rem !important + } + + .pr-lg-6 { + padding-right: 2rem !important + } + + .pb-lg-6 { + padding-bottom: 2rem !important + } + + .pl-lg-6 { + padding-left: 2rem !important + } + + .px-lg-6 { + padding-right: 2rem !important; + padding-left: 2rem !important + } + + .py-lg-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important + } + + .p-lg-7 { + padding: 2.5rem !important + } + + .pt-lg-7 { + padding-top: 2.5rem !important + } + + .pr-lg-7 { + padding-right: 2.5rem !important + } + + .pb-lg-7 { + padding-bottom: 2.5rem !important + } + + .pl-lg-7 { + padding-left: 2.5rem !important + } + + .px-lg-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important + } + + .py-lg-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important + } + + .p-lg-8 { + padding: 3rem !important + } + + .pt-lg-8 { + padding-top: 3rem !important + } + + .pr-lg-8 { + padding-right: 3rem !important + } + + .pb-lg-8 { + padding-bottom: 3rem !important + } + + .pl-lg-8 { + padding-left: 3rem !important + } + + .px-lg-8 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-lg-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .p-lg-9 { + padding: 3.5rem !important + } + + .pt-lg-9 { + padding-top: 3.5rem !important + } + + .pr-lg-9 { + padding-right: 3.5rem !important + } + + .pb-lg-9 { + padding-bottom: 3.5rem !important + } + + .pl-lg-9 { + padding-left: 3.5rem !important + } + + .px-lg-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important + } + + .py-lg-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important + } + + .p-lg-10 { + padding: 4rem !important + } + + .pt-lg-10 { + padding-top: 4rem !important + } + + .pr-lg-10 { + padding-right: 4rem !important + } + + .pb-lg-10 { + padding-bottom: 4rem !important + } + + .pl-lg-10 { + padding-left: 4rem !important + } + + .px-lg-10 { + padding-right: 4rem !important; + padding-left: 4rem !important + } + + .py-lg-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important + } +} + +@media (min-width: 87.5rem) { + .p-xl-0 { + padding: 0 !important + } + + .pt-xl-0 { + padding-top: 0 !important + } + + .pr-xl-0 { + padding-right: 0 !important + } + + .pb-xl-0 { + padding-bottom: 0 !important + } + + .pl-xl-0 { + padding-left: 0 !important + } + + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important + } + + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important + } + + .p-xl-1 { + padding: .25rem !important + } + + .pt-xl-1 { + padding-top: .25rem !important + } + + .pr-xl-1 { + padding-right: .25rem !important + } + + .pb-xl-1 { + padding-bottom: .25rem !important + } + + .pl-xl-1 { + padding-left: .25rem !important + } + + .px-xl-1 { + padding-right: .25rem !important; + padding-left: .25rem !important + } + + .py-xl-1 { + padding-top: .25rem !important; + padding-bottom: .25rem !important + } + + .p-xl-2 { + padding: .5rem !important + } + + .pt-xl-2 { + padding-top: .5rem !important + } + + .pr-xl-2 { + padding-right: .5rem !important + } + + .pb-xl-2 { + padding-bottom: .5rem !important + } + + .pl-xl-2 { + padding-left: .5rem !important + } + + .px-xl-2 { + padding-right: .5rem !important; + padding-left: .5rem !important + } + + .py-xl-2 { + padding-top: .5rem !important; + padding-bottom: .5rem !important + } + + .p-xl-3 { + padding: .75rem !important + } + + .pt-xl-3 { + padding-top: .75rem !important + } + + .pr-xl-3 { + padding-right: .75rem !important + } + + .pb-xl-3 { + padding-bottom: .75rem !important + } + + .pl-xl-3 { + padding-left: .75rem !important + } + + .px-xl-3 { + padding-right: .75rem !important; + padding-left: .75rem !important + } + + .py-xl-3 { + padding-top: .75rem !important; + padding-bottom: .75rem !important + } + + .p-xl-4 { + padding: 1rem !important + } + + .pt-xl-4 { + padding-top: 1rem !important + } + + .pr-xl-4 { + padding-right: 1rem !important + } + + .pb-xl-4 { + padding-bottom: 1rem !important + } + + .pl-xl-4 { + padding-left: 1rem !important + } + + .px-xl-4 { + padding-right: 1rem !important; + padding-left: 1rem !important + } + + .py-xl-4 { + padding-top: 1rem !important; + padding-bottom: 1rem !important + } + + .p-xl-5 { + padding: 1.5rem !important + } + + .pt-xl-5 { + padding-top: 1.5rem !important + } + + .pr-xl-5 { + padding-right: 1.5rem !important + } + + .pb-xl-5 { + padding-bottom: 1.5rem !important + } + + .pl-xl-5 { + padding-left: 1.5rem !important + } + + .px-xl-5 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important + } + + .py-xl-5 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important + } + + .p-xl-6 { + padding: 2rem !important + } + + .pt-xl-6 { + padding-top: 2rem !important + } + + .pr-xl-6 { + padding-right: 2rem !important + } + + .pb-xl-6 { + padding-bottom: 2rem !important + } + + .pl-xl-6 { + padding-left: 2rem !important + } + + .px-xl-6 { + padding-right: 2rem !important; + padding-left: 2rem !important + } + + .py-xl-6 { + padding-top: 2rem !important; + padding-bottom: 2rem !important + } + + .p-xl-7 { + padding: 2.5rem !important + } + + .pt-xl-7 { + padding-top: 2.5rem !important + } + + .pr-xl-7 { + padding-right: 2.5rem !important + } + + .pb-xl-7 { + padding-bottom: 2.5rem !important + } + + .pl-xl-7 { + padding-left: 2.5rem !important + } + + .px-xl-7 { + padding-right: 2.5rem !important; + padding-left: 2.5rem !important + } + + .py-xl-7 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important + } + + .p-xl-8 { + padding: 3rem !important + } + + .pt-xl-8 { + padding-top: 3rem !important + } + + .pr-xl-8 { + padding-right: 3rem !important + } + + .pb-xl-8 { + padding-bottom: 3rem !important + } + + .pl-xl-8 { + padding-left: 3rem !important + } + + .px-xl-8 { + padding-right: 3rem !important; + padding-left: 3rem !important + } + + .py-xl-8 { + padding-top: 3rem !important; + padding-bottom: 3rem !important + } + + .p-xl-9 { + padding: 3.5rem !important + } + + .pt-xl-9 { + padding-top: 3.5rem !important + } + + .pr-xl-9 { + padding-right: 3.5rem !important + } + + .pb-xl-9 { + padding-bottom: 3.5rem !important + } + + .pl-xl-9 { + padding-left: 3.5rem !important + } + + .px-xl-9 { + padding-right: 3.5rem !important; + padding-left: 3.5rem !important + } + + .py-xl-9 { + padding-top: 3.5rem !important; + padding-bottom: 3.5rem !important + } + + .p-xl-10 { + padding: 4rem !important + } + + .pt-xl-10 { + padding-top: 4rem !important + } + + .pr-xl-10 { + padding-right: 4rem !important + } + + .pb-xl-10 { + padding-bottom: 4rem !important + } + + .pl-xl-10 { + padding-left: 4rem !important + } + + .px-xl-10 { + padding-right: 4rem !important; + padding-left: 4rem !important + } + + .py-xl-10 { + padding-top: 4rem !important; + padding-bottom: 4rem !important + } +} + +@media print { + .site-footer, .site-button, #edit-this-page, #back-to-top, .site-nav, .main-header { + display: none !important + } + + .side-bar { + width: 100%; + height: auto; + border-right: 0 !important + } + + .site-header { + border-bottom: 1px solid #eeebee + } + + .site-title { + font-size: 16px !important; + font-weight: 700 !important + } + + .text-small { + font-size: 8pt !important + } + + pre.highlight { + border: 1px solid #eeebee + } + + .main { + max-width: none; + margin-left: 0 + } +} diff --git a/docs/assets/files/DSC_implementation_concept_ids_ready_v4.pdf b/docs/assets/files/DSC_implementation_concept_ids_ready_v4.pdf new file mode 100644 index 000000000..8c84fe938 Binary files /dev/null and b/docs/assets/files/DSC_implementation_concept_ids_ready_v4.pdf differ diff --git a/docs/assets/files/dsc_v4_wiki.zip b/docs/assets/files/dsc_v4_wiki.zip new file mode 100644 index 000000000..6155f4199 Binary files /dev/null and b/docs/assets/files/dsc_v4_wiki.zip differ diff --git a/docs/assets/images/data_model.png b/docs/assets/images/data_model.png new file mode 100644 index 000000000..0f13765fa Binary files /dev/null and b/docs/assets/images/data_model.png differ diff --git a/docs/assets/images/data_sharing.png b/docs/assets/images/data_sharing.png new file mode 100644 index 000000000..dfefec311 Binary files /dev/null and b/docs/assets/images/data_sharing.png differ diff --git a/docs/assets/images/dsc_architecture.png b/docs/assets/images/dsc_architecture.png new file mode 100644 index 000000000..a952bbd1a Binary files /dev/null and b/docs/assets/images/dsc_architecture.png differ diff --git a/docs/assets/images/dsc_logo.png b/docs/assets/images/dsc_logo.png new file mode 100644 index 000000000..0fc5b43d0 Binary files /dev/null and b/docs/assets/images/dsc_logo.png differ diff --git a/docs/assets/images/ids_ecosystem.png b/docs/assets/images/ids_ecosystem.png new file mode 100644 index 000000000..4a9005245 Binary files /dev/null and b/docs/assets/images/ids_ecosystem.png differ diff --git a/docs/assets/images/message_sequence_1.png b/docs/assets/images/message_sequence_1.png new file mode 100644 index 000000000..148ff0e2d Binary files /dev/null and b/docs/assets/images/message_sequence_1.png differ diff --git a/docs/assets/images/message_sequence_2.png b/docs/assets/images/message_sequence_2.png new file mode 100644 index 000000000..354ec248a Binary files /dev/null and b/docs/assets/images/message_sequence_2.png differ diff --git a/docs/assets/images/mvc_implementation.png b/docs/assets/images/mvc_implementation.png new file mode 100644 index 000000000..119b576ac Binary files /dev/null and b/docs/assets/images/mvc_implementation.png differ diff --git a/docs/assets/images/negotiation_settings.png b/docs/assets/images/negotiation_settings.png new file mode 100644 index 000000000..486abce28 Binary files /dev/null and b/docs/assets/images/negotiation_settings.png differ diff --git a/docs/assets/images/pattern_settings.png b/docs/assets/images/pattern_settings.png new file mode 100644 index 000000000..169743d77 Binary files /dev/null and b/docs/assets/images/pattern_settings.png differ diff --git a/images/architecture.png b/docs/assets/images/ram_architecture.png similarity index 100% rename from images/architecture.png rename to docs/assets/images/ram_architecture.png diff --git a/docs/assets/images/rest_api.png b/docs/assets/images/rest_api.png new file mode 100644 index 000000000..d562c02f0 Binary files /dev/null and b/docs/assets/images/rest_api.png differ diff --git a/docs/assets/images/rest_offer.png b/docs/assets/images/rest_offer.png new file mode 100644 index 000000000..8416b3e5d Binary files /dev/null and b/docs/assets/images/rest_offer.png differ diff --git a/docs/assets/images/swagger_artifact_data.png b/docs/assets/images/swagger_artifact_data.png new file mode 100644 index 000000000..143fdb6fa Binary files /dev/null and b/docs/assets/images/swagger_artifact_data.png differ diff --git a/docs/assets/images/swagger_catalogs_offer.png b/docs/assets/images/swagger_catalogs_offer.png new file mode 100644 index 000000000..4cd4f75cc Binary files /dev/null and b/docs/assets/images/swagger_catalogs_offer.png differ diff --git a/docs/assets/images/swagger_communication_endpoints.png b/docs/assets/images/swagger_communication_endpoints.png new file mode 100644 index 000000000..bc30fe2cb Binary files /dev/null and b/docs/assets/images/swagger_communication_endpoints.png differ diff --git a/docs/assets/images/swagger_connector.png b/docs/assets/images/swagger_connector.png new file mode 100644 index 000000000..eb793894a Binary files /dev/null and b/docs/assets/images/swagger_connector.png differ diff --git a/docs/assets/images/swagger_contract_request.png b/docs/assets/images/swagger_contract_request.png new file mode 100644 index 000000000..68d4d66bb Binary files /dev/null and b/docs/assets/images/swagger_contract_request.png differ diff --git a/docs/assets/images/swagger_description_request.png b/docs/assets/images/swagger_description_request.png new file mode 100644 index 000000000..ad5dca10e Binary files /dev/null and b/docs/assets/images/swagger_description_request.png differ diff --git a/docs/assets/images/swagger_example_catalogs_offer.png b/docs/assets/images/swagger_example_catalogs_offer.png new file mode 100644 index 000000000..4f6f918f3 Binary files /dev/null and b/docs/assets/images/swagger_example_catalogs_offer.png differ diff --git a/docs/assets/images/swagger_offer.png b/docs/assets/images/swagger_offer.png new file mode 100644 index 000000000..c1d87a6a5 Binary files /dev/null and b/docs/assets/images/swagger_offer.png differ diff --git a/docs/assets/images/swagger_offer_catalogs.png b/docs/assets/images/swagger_offer_catalogs.png new file mode 100644 index 000000000..b0847d11c Binary files /dev/null and b/docs/assets/images/swagger_offer_catalogs.png differ diff --git a/docs/assets/images/swagger_policy.png b/docs/assets/images/swagger_policy.png new file mode 100644 index 000000000..3db46757a Binary files /dev/null and b/docs/assets/images/swagger_policy.png differ diff --git a/docs/assets/images/swagger_resource_updates.png b/docs/assets/images/swagger_resource_updates.png new file mode 100644 index 000000000..e0f19eb69 Binary files /dev/null and b/docs/assets/images/swagger_resource_updates.png differ diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 000000000..0fc5b43d0 Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..0fa1a77d1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,65 @@ +--- +layout: default +title: Home +nav_order: 1 +description: "The Dataspace Connector is an IDS connector reference implementation following the specifications of the IDS Information Model." +permalink: / +--- + +# Manual and Documentation +{: .fs-9 } + +This documentation should help you to become familiar with the Dataspace Connector - to try it out, +integrate it in your use cases, or contribute to its development. +{: .fs-6 .fw-300 } + +[Get started now](pages/getting-started.md){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [View it on GitHub](https://github.com/International-Data-Spaces-Association/DataspaceConnector){: .btn .fs-5 .mb-4 .mb-md-0 } + +--- + +The Dataspace Connector is an IDS connector that is being developed at Fraunhofer ISST. With the +help of the Dataspace Connector, existing software can easily be extended by IDS connector +functionalities in order to integrate them into an IDS data ecosystem. Furthermore, it is possible +to use the Dataspace Connector as a basis for the development of own software that is to be +connected to an IDS data ecosystem. + +The Dataspace Connector uses the recent IDS Information Model version and the IDS Framework of +Fraunhofer ISST for message handling with other IDS components. For managing datasets by means of +their metadata as IDS resources, the Dataspace Connector provides a REST API. After an initial +registration, IDS resources are persisted to an internal or external database of the connector. +External data sources can be connected via REST endpoints, allowing the Dataspace Connector to act +as an intermediary between the IDS data ecosystem and the actual data source. + +Following the requirements of the International Data Spaces, TLS-encrypted communication with other +IDS connectors and, for example, communication with an IDS broker are supported in the context of an +IDS data ecosystem. The Dataspace Connector can simultaneously act as both a data provider and a +data consumer, and thus both provide data in a data ecosystem and request it from other IDS +connectors. The Dataspace Connector supports various usage control rules, which are implemented and +enforced. This allows data in the IDS data ecosystem to be assigned usage control rules and ensures +data sovereignty throughout the data lifecycle. Furthermore, identity management is supported by the +integration of an identity provider in the IDS context, such as a DAPS. + +The Dataspace Connector is an open source project whose development is being driven in collaboration +with various research institutes and companies. Its architecture allows the existing implementation +to be adapted as needed for domain-specific requirements. The deployment of the Dataspace Connector +can be run in Docker as well as in Kubernetes. + +## IDS-ready + +Find the implementation concept [here](assets/files/DSC_implementation_concept_ids_ready_v4.pdf). + + + +"The aim of the Dataspace Connector is to provide companies with an easy and trustworthy entry into +the International Data Spaces. There are three levels of certification for the International Data +Spaces, an initiative for cross-industry data exchange with over 100 European companies: Base, +Trusted and Trusted+. The DSC was deliberately tested for the Base certification level, as this does +not require specific hardware such as Trusted Platform Module chips in order to use the connector. +This makes it easier to use the DSC on different hardware and in cloud environments with a +reasonable sacrifice of hardware security features. + +In addition, the Dataspace Connector is the only IDS connector that already supports the enforcement +of eight usage condition classes of the International Data Spaces Association and thus exceeds the +Base certification level." + +— [News Release](https://www.isst.fraunhofer.de/de/news/pressemitteilungen/2020/Dataspace-Connector.html) diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..0f5820439 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,23 @@ +{ + "name": "just-the-docs", + "version": "0.3.3", + "description": "A modern Jekyll theme for documentation", + "repository": "pmarsceill/just-the-docs", + "license": "MIT", + "bugs": "https://github.com/pmarsceill/just-the-docs/issues", + "devDependencies": { + "stylelint": "^13.7.2", + "@primer/css": "^15.2.0", + "prettier": "^2.1.2", + "stylelint-config-prettier": "^8.0.2", + "stylelint-config-primer": "^9.2.1", + "stylelint-prettier": "^1.1.2", + "stylelint-selector-no-utility": "^4.0.0" + }, + "scripts": { + "test": "stylelint '**/*.scss'", + "format": "prettier --write '**/*.{scss,js,json}'", + "stylelint-check": "stylelint-config-prettier-check" + }, + "dependencies": {} +} diff --git a/docs/pages/communication.md b/docs/pages/communication.md new file mode 100644 index 000000000..e25d964cf --- /dev/null +++ b/docs/pages/communication.md @@ -0,0 +1,32 @@ +--- +layout: default +title: Communication Guide +nav_order: 6 +description: "" +permalink: /CommunicationGuide +has_children: true +has_toc: true +--- + +# Communication Guide +{: .fs-9 } + +This page explains how to use the APIs of the connector. +{: .fs-6 .fw-300 } + +--- + +You have configured and deployed your Dataspace Connector as described [here](deployment.md)? Then +check out our step-by-step guide to see the Connector in action. + +To interact with the running application, the provided +[endpoints](deployment/build.md#maven) can be used - either automated by an application or manually +by interacting with the Swagger UI. In this section, it is explained how to provide local and remote +data with a connector and how to consume this from another one. + +--- + +**Note**: The Dataspace Connector's repository comes with a `scripts/tests` folder that provides +some Python scripts. They contain the creation of a full data offering and its consumption by a +consumer: for a single resource with a single usage policy, a single resource with multiple usage +policies, and providing and requesting multiple artifacts at once. diff --git a/docs/pages/communication/consumer.md b/docs/pages/communication/consumer.md new file mode 100644 index 000000000..898db1861 --- /dev/null +++ b/docs/pages/communication/consumer.md @@ -0,0 +1,408 @@ +--- +layout: default +title: Consumer +nav_order: 2 +description: "" +permalink: /CommunicationGuide/Consumer +parent: Communication Guide +--- + +# Consuming Data +{: .fs-9 } + +See how to consume data with the Dataspace Connector. +{: .fs-6 .fw-300 } + +--- + +The connector provides an endpoint for requesting its self-description. +The self-description is returned as JSON-LD and contains several information about the running +connector instance. This includes e.g. the title, the maintainer, the IDS Informodel version, and +the resource catalog. At the public endpoint `/`, the resource catalog is not displayed. It can only +be accessed with admin credentials at `GET /api/connector` or by sending an IDS description request +message as explained [here](consumer.md#step-1-request-a-connectors-self-description)). + +![Selfservice Endpoints](../../assets/images/swagger_connector.png) + +## Step by Step + +For requesting data and metadata as a data consumer, two endpoints are provided. A description +request is used for requesting the metadata and a contract request is used for handling out +contract agreements as a condition to retrieve raw data from a data provider. + +![Connector Communication Endpoints](../../assets/images/swagger_communication_endpoints.png) + +--- + +**Important**: Note that the `/api/ids/data` endpoint may not be valid for other connector +implementations. Check at which endpoint the data provider expects the IDS multipart messages in +advance. + +--- + +### Step 1: Request a Connector's Self-description + +For sending a `POST` request, two parameters have to be set: the recipient and the requested element. +As the data consumer needs to access the self-description of a data provider to know all resource +offers, the requested element should be empty. + +![Description Request](../../assets/images/swagger_description_request.png) + +If the request is successful, the response body will contain a `BaseConnector` with a single catalog +or list of catalogs. + +````json +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:BaseConnector", + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea", + "ids:publicKey" : { + "@type" : "ids:PublicKey", + "@id" : "https://w3id.org/idsa/autogen/publicKey/78eb73a3-3a2a-4626-a0ff-631ab50a00f9", + "ids:keyType" : { + "@id" : "idsc:RSA" + }, + "ids:keyValue" : "[...]" + }, + "ids:version" : "5.0.0", + "ids:description" : [ { + "@value" : "IDS Connector with static example resources hosted by the Fraunhofer ISST", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:title" : [ { + "@value" : "Dataspace Connector", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:securityProfile" : { + "@id" : "idsc:BASE_SECURITY_PROFILE" + }, + "ids:maintainer" : { + "@id" : "https://www.isst.fraunhofer.de/" + }, + "ids:resourceCatalog" : [ { + "@type" : "ids:ResourceCatalog", + "@id" : "https://localhost:8080/api/catalogs/eda0cda2-10f2-4b39-b462-5d4f2b1bb758" + } ], + "ids:curator" : { + "@id" : "https://www.isst.fraunhofer.de/" + }, + "ids:hasDefaultEndpoint" : { + "@type" : "ids:ConnectorEndpoint", + "@id" : "https://w3id.org/idsa/autogen/connectorEndpoint/e5e2ab04-633a-44b9-87d9-a097ae6da3cf", + "ids:accessURL" : { + "@id" : "https://localhost:8080/api/ids/data" + } + }, + "ids:inboundModelVersion" : [ "4.0.0", "4.0.4" ], + "ids:outboundModelVersion" : "4.0.4" +} +```` + +### Step 2: Request Metadata + +To request the metadata of a specific catalog, resource, representation, artifact, or contract, use +the same description request endpoint and put the value of `@id` as requested element. + +If your DAT within the `DescriptionRequestMessage` was not valid, the requested element could not be +found, or any other error occurred, you will receive a `RejectionMessage` with an according +rejection reason from the provider connector. + +````json +{ + "reason": { + "properties": null, + "@id": "idsc:NOT_FOUND" + }, + "payload": "The requested element https://localhost:8080/api/catalogs/5ac012e1-ffa5-43b3-af41-9707d2a9137e could not be found.", + "type": "de.fraunhofer.iais.eis.RejectionMessageImpl" +} +```` + +With this, you can navigate yourself through the data offers of the provider and choose the artifact +whose data you want to retrieve. A response will never contain the raw data. + +Following the example data, that was provided within the [provider guide](provider.md), we would end +up with the following information when requesting +[https://localhost:8080/api/catalogs/eda0cda2-10f2-4b39-b462-5d4f2b1bb758](https://localhost:8080/api/catalogs/eda0cda2-10f2-4b39-b462-5d4f2b1bb758) +and its resource offer [https://localhost:8080/api/offers/98d6818b-a1b7-4171-a318-a0e11837bf10](https://localhost:8080/api/offers/98d6818b-a1b7-4171-a318-a0e11837bf10): + +````json +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:Resource", + "@id" : "https://localhost:8080/api/offers/98d6818b-a1b7-4171-a318-a0e11837bf10", + "ids:language" : [ { + "@id" : "idsc:EN" + } ], + "ids:created" : { + "@value" : "2021-05-17T18:58:39.351Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:version" : "1", + "ids:description" : [ { + "@value" : "This is an example resource containing weather data.", + "@language" : "EN" + } ], + "ids:title" : [ { + "@value" : "Sample Resource", + "@language" : "EN" + } ], + "ids:sovereign" : { + "@id" : "https://openweathermap.org/" + }, + "ids:publisher" : { + "@id" : "https://openweathermap.org/" + }, + "ids:representation" : [ { + "@type" : "ids:Representation", + "@id" : "https://localhost:8080/api/representations/53c05406-23b2-4ca1-8d39-063681944412", + "ids:instance" : [ { + "@type" : "ids:Artifact", + "@id" : "https://localhost:8080/api/artifacts/9bb8162b-a754-43ed-a590-f50645bbf220", + "ids:fileName" : "", + "ids:creationDate" : { + "@value" : "2021-05-17T18:58:39.534Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:byteSize" : 0, + "ids:checkSum" : "0" + } ], + "ids:language" : { + "@id" : "idsc:EN" + }, + "ids:created" : { + "@value" : "2021-05-17T18:58:39.443Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:mediaType" : { + "@type" : "ids:IANAMediaType", + "@id" : "https://w3id.org/idsa/autogen/iANAMediaType/b6dca6fa-842b-4144-ac26-2988c884d5e8", + "ids:filenameExtension" : "" + }, + "ids:modified" : { + "@value" : "2021-05-17T18:58:39.443Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:representationStandard" : { + "@id" : "" + } + } ], + "ids:resourceEndpoint" : [ { + "@type" : "ids:ConnectorEndpoint", + "@id" : "https://w3id.org/idsa/autogen/connectorEndpoint/da3f1e1c-1d3a-45cf-8683-93b8c090a601", + "ids:endpointDocumentation" : [ { + "@id" : "https://example.com" + } ], + "ids:accessURL" : { + "@id" : "https://localhost:8080/api/offers/98d6818b-a1b7-4171-a318-a0e11837bf10" + } + } ], + "ids:contractOffer" : [ { + "@type" : "ids:ContractOffer", + "@id" : "https://localhost:8080/api/contracts/94c31f67-aec9-43d4-a7ca-887cc1aaced1", + "ids:permission" : [ { + "@type" : "ids:Permission", + "@id" : "https://localhost:8080/api/rules/c5d94d73-f3b1-4b4d-b003-0c2e91e221c0", + "ids:description" : [ { + "@value" : "provide-access", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:title" : [ { + "@value" : "Example Usage Policy", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:action" : [ { + "@id" : "idsc:USE" + } ] + } ], + "ids:provider" : { + "@id" : "" + }, + "ids:consumer" : { + "@id" : "" + }, + "ids:contractEnd" : { + "@value" : "2021-05-17T18:58:39.626Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:obligation" : [ ], + "ids:prohibition" : [ ], + "ids:contractDate" : { + "@value" : "2021-05-17T21:01:26.576+02:00", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:contractStart" : { + "@value" : "2021-05-17T18:58:39.626Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + } + } ], + "ids:keyword" : [ { + "@value" : "weather", + "@language" : "EN" + }, { + "@value" : "data", + "@language" : "EN" + }, { + "@value" : "sample", + "@language" : "EN" + } ], + "ids:standardLicense" : { + "@id" : "http://opendatacommons.org/licenses/odbl/1.0/" + }, + "ids:modified" : { + "@value" : "2021-05-17T18:58:39.351Z", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "key" : "value" +} +```` + +### Step 3: Negotiate a Contract Agreement + +As you are not allowed to retrieve data from a provider without a matching contract agreement, you +first have to initiate a contract negotiation. A single resource can contain multiple +representations, therefore, the data consumer needs to check all available artifacts in the +requested metadata and choose one for the data request. + +Within the description response, you receive the resource's metadata containing a contract offer. +Use the provided endpoint to put the received contract offer's rule or a modified one in the request +body and start the contract negotiation for a specific artifact and resource. You will agree to the +provided contract offer by using it for the contract request without content changes. You just have +to add the artifact id as `ids:target` to the rule. + +If you provide wrong inputs, you will get a response body with a hint on what went wrong. + +![Contract Request](../../assets/images/swagger_contract_request.png) + +With the `download` value you may specify whether you want the Dataspace Connector to download the +data immediately or later. + +For our example, a correct request would look like that: + +``` +curl -X 'POST' \ + 'https://localhost:8080/api/ids/contract?recipient=https%3A%2F%2Flocalhost%3A8080%2Fapi%2Fids%2Fdata&resourceIds=https%3A%2F%2Flocalhost%3A8080%2Fapi%2Foffers%2F98d6818b-a1b7-4171-a318-a0e11837bf10&artifactIds=https%3A%2F%2Flocalhost%3A8080%2Fapi%2Fartifacts%2F9bb8162b-a754-43ed-a590-f50645bbf220&download=true' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '[ + { + "@type" : "ids:Permission", + "@id" : "https://localhost:8080/api/rules/c5d94d73-f3b1-4b4d-b003-0c2e91e221c0", + "ids:description" : [ { + "@value" : "provide-access", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:title" : [ { + "@value" : "Example Usage Policy", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:action" : [ { + "@id" : "idsc:USE" + } ], + "ids:target": "https://localhost:8080/api/artifacts/9bb8162b-a754-43ed-a590-f50645bbf220" + } +]' +``` + +The rule list will be automatically turned into a contract request to then send it to the provider. +This will read this contract request, compare it to the artifact's (respectively the corresponding +resource's) contract offers, and return either a `ContractRejectionMessage` or a +`ContractAgreementMessage`. + +As a response, we now receive the closed contract agreement: + +``` +{ + "creationDate": "2021-05-17T21:16:01.050+0200", + "modificationDate": "2021-05-17T21:16:01.050+0200", + "remoteId": "https://localhost:8080/api/agreements/fb45ebeb-55a8-4aa6-bdd3-3765b067db2b", + "confirmed": true, + "value": "{\r\n \"@context\" : {\r\n \"ids\" : \"https://w3id.org/idsa/core/\"\r\n },\r\n \"@type\" : \"ids:ContractAgreement\",\r\n \"@id\" : \"https://localhost:8080/api/agreements/fb45ebeb-55a8-4aa6-bdd3-3765b067db2b\",\r\n \"ids:permission\" : [ {\r\n \"@type\" : \"ids:Permission\",\r\n \"@id\" : \"https://localhost:8080/api/rules/c5d94d73-f3b1-4b4d-b003-0c2e91e221c0\",\r\n \"ids:target\" : {\r\n \"@id\" : \"https://localhost:8080/api/artifacts/9bb8162b-a754-43ed-a590-f50645bbf220\"\r\n },\r\n \"ids:description\" : [ {\r\n \"@value\" : \"provide-access\",\r\n \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\r\n } ],\r\n \"ids:title\" : [ {\r\n \"@value\" : \"Example Usage Policy\",\r\n \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\r\n } ],\r\n \"ids:assignee\" : [ {\r\n \"@id\" : \"https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea\"\r\n } ],\r\n \"ids:assigner\" : [ {\r\n \"@id\" : \"https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea\"\r\n } ],\r\n \"ids:action\" : [ {\r\n \"@id\" : \"idsc:USE\"\r\n } ]\r\n } ],\r\n \"ids:provider\" : {\r\n \"@id\" : \"https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea\"\r\n },\r\n \"ids:consumer\" : {\r\n \"@id\" : \"https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea\"\r\n },\r\n \"ids:obligation\" : [ ],\r\n \"ids:prohibition\" : [ ],\r\n \"ids:contractDate\" : {\r\n \"@value\" : \"2021-05-17T21:16:00.849+02:00\",\r\n \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n },\r\n \"ids:contractStart\" : {\r\n \"@value\" : \"2021-05-17T21:16:00.849+02:00\",\r\n \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n }\r\n}", + "_links": { + "self": { + "href": "https://localhost:8080/api/agreements/e6464bac-bed9-49ca-bafb-86fb4142b49c" + }, + "artifacts": { + "href": "https://localhost:8080/api/agreements/e6464bac-bed9-49ca-bafb-86fb4142b49c/artifacts{?page,size,sort}", + "templated": true + } + } +} +``` + +The corresponding contract agreement has been sent to the Clearing House and stored inside the +consumer's and provider's internal database for later access and usage control. + +If we change e.g. the `ids:action` from `idsc:USE` to `idsc:MODIFY`, we will receive a +`RejectionMessage` from the provider: + +``` +{ + "reason": { + "properties": null, + "@id": "idsc:MALFORMED_MESSAGE" + }, + "payload": "Contract rejected.", + "type": "de.fraunhofer.iais.eis.ContractRejectionMessageImpl" +} +``` + +--- + +**Note**: As the endpoint for a contract request expects a list of resource IDs, artifact IDS, and +rules, you are able to handle out contract agreements for multiple artifacts at once. + +--- + +The Dataspace Connector will automatically start sending `DescriptionRequestMessages` and +`ArtifactRequestMessages` for the requested elements and save metadata and data to its database. + +### Step 4: Access the Data + +Within the contract agreement, you can find a link to all negotiated artifacts. These provide +information on how to access the data string. Depending on whether data was already provided or +should be updated each time again, the Dataspace Connector as a data consumer automatically starts +sending `ArtifactRequestMessages` with the correct artifact id and transfer contract id to request +the data from the consumer. + +You may also set the `download` value manually on a data request or specify what agreement should be +used. + +![Data Request](../../assets/images/swagger_artifact_data.png) + +Either way, the requested and downloaded data will be stored in the database as a bytestream and +is automatically decoded on an API call. + + +## Policy Enforcement + +As artifact and contract agreements are linked inside the Dataspace Connector's database, the usage +policies of the requested data resource are checked for the following patterns: +`USAGE_DURING_INTERVAL`, `DURATION_USAGE`, `USAGE_UNTIL_DELETION`, `USAGE_LOGGING`, +`USAGE_NOTIFICATION`, and `N_TIMES_USAGE`. The policy is then implemented using the detected +pattern. + +As described [here](provider.md#policy-enforcement), depending on the rule values, the access +permission will be set to true or false, and correspondingly, the data is either returned or not. + +On top of that, the Dataspace Connector performs a periodic policy check. If a duty within a +contract agreement determines the deletion date and time, as in `USAGE_UNTIL_DELETION`, is detected, +usage control is executed and the concerned data is deleted. + +## Resource Updates + +If the Dataspace Connector receives a `ResourceUpdateMessage` for a known requested resource, it +automatically sends a `DescriptionRequestMessage` and an `ArtifactRequestMessage` to retrieve the +latest metadata and data. + +## Parametrized Data Request + +If the provider allows to query the data that you want to consume, you are able to make generic +`GET` requests on `/api/artifacts/{id}/data/**`. Any request headers, parameters, or path variables +will be forwarded to the provider and you will only receive a snapshot of the offered data string. diff --git a/docs/pages/communication/provider.md b/docs/pages/communication/provider.md new file mode 100644 index 000000000..973261a85 --- /dev/null +++ b/docs/pages/communication/provider.md @@ -0,0 +1,308 @@ +--- +layout: default +title: Provider +nav_order: 1 +description: "" +permalink: /CommunicationGuide/Provider +parent: Communication Guide +--- + +# Providing Data +{: .fs-9 } + +See how to provide data with the Dataspace Connector. +{: .fs-6 .fw-300 } + +--- + +The connector provides an endpoint for requesting its self-description. +The self-description is returned as JSON-LD and contains several information about the running +connector instance. This includes e.g. the title, the maintainer, the IDS Informodel version, and +the resource catalog. At the public endpoint `/`, the resource catalog is not displayed. It can only +be accessed with admin credentials at `GET /api/connector` or by sending an IDS description request +message as explained [here](consumer.md#step-1-request-a-connectors-self-description). + +![Selfservice Endpoints](../../assets/images/swagger_connector.png) + +## Step by Step + +To understand the structure of a resource, please first take a look at the +[data model section](../documentation/data-model.md) and the [REST API explanation](../documentation/rest-api.md). +For adding resources to the running connector as a data provider, have a look at the following +steps. + +In the following example, we want to provide the raw +[data](https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02) +to a data consumer. + +### Step 1: Register Data Resources + +The endpoint `POST /api/offers` can be used for registering a new resource offer at the connector. +This can be done by providing information as metadata in JSON format. An example will be explained +in the following. + +``` +curl -X 'POST' \ + 'https://localhost:8080/api/offers' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '{ + "title": "Sample Resource", + "description": "This is an example resource containing weather data.", + "keywords": [ + "weather", + "data", + "sample" + ], + "publisher": "https://openweathermap.org/", + "language": "EN", + "licence": "http://opendatacommons.org/licenses/odbl/1.0/", + "sovereign": "https://openweathermap.org/", + "endpointDocumentation": "https://example.com", + "key": "value" +}' +``` + +The values `title`, `description`, `keywords`, `publisher`, `sovereign`, `license`, etc. describe +the data resource and will be used to fill in the IDS Information Model attributes for IDS +communication with a connector as data consumer. + +--- + +**Note**: If you need any further attributes, feel free to just type custom key value pairs. They +will be stored as `additional` inside the database - as shown in this example. + +--- + +See the response of the previously registered resource below. + +Response headers: + +``` +cache-control: no-cache,no-store,max-age=0,must-revalidate + connection: keep-alive + content-type: application/hal+json + date: Mon,17 May 2021 17:16:53 GMT + expires: 0 + keep-alive: timeout=60 + location: https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623 + pragma: no-cache + strict-transport-security: max-age=31536000 ; includeSubDomains + transfer-encoding: chunked + x-content-type-options: nosniff + x-frame-options: DENY + x-xss-protection: 1; mode=block +``` + +If the resource was successfully registered, the endpoint will respond with `Http.CREATED` and the +`location`field of the created resource within the response header. + +Response body: +```json +{ + "creationDate": "2021-05-17T19:16:53.385+0200", + "modificationDate": "2021-05-17T19:16:53.385+0200", + "title": "Sample Resource", + "description": "This is an example resource containing weather data.", + "keywords": [ + "weather", + "data", + "sample" + ], + "publisher": "https://openweathermap.org/", + "language": "EN", + "licence": "http://opendatacommons.org/licenses/odbl/1.0/", + "version": 1, + "sovereign": "https://openweathermap.org/", + "endpointDocumentation": "https://example.com", + "additional": { + "key": "value" + }, + "_links": { + "self": { + "href": "https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623" + }, + "contracts": { + "href": "https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623/contracts{?page,size,sort}", + "templated": true + }, + "representations": { + "href": "https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623/representations{?page,size,sort}", + "templated": true + }, + "catalogs": { + "href": "https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623/catalogs{?page,size,sort}", + "templated": true + } + } +} +``` + +Apart from the metadata, the response body contains links to further points of interest, such as +itself, its parents, etc. A version number is generated automatically and is increased with every +entity change, as well as the creation and modification date. + +The endpoints `PUT`, `GET`, and`DELETE` `/offers/{id}` provide standard CRUD functions to read, +update, and delete the metadata, respectively the data resource - as described +[here](../documentation/data-model.md). + +Next to the resource, we need a catalog as a parent for the offer. Use `POST /api/catalogs` to +create one. Its location is: [https://localhost:8080/api/catalogs/5ac012e1-ffa5-43b3-af41-9707d2a9137d](https://localhost:8080/api/catalogs/5ac012e1-ffa5-43b3-af41-9707d2a9137d). +Then, we need to link both objects to each other via another endpoint. Therefore, we execute a `POST` +catalog's id extended by `/offers` and the resource's id as part of the list in the request body. + +![Example Offer Catalog](../../assets/images/swagger_example_catalogs_offer.png) + +``` +curl -X 'POST' \ + 'https://localhost:8080/api/catalogs/5ac012e1-ffa5-43b3-af41-9707d2a9137d/offers' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '[ + "https://localhost:8080/api/offers/ca502fbc-fbeb-4125-bd65-97536647d623" +]' +``` + +As stated [here](../documentation/data-model.md), **an offered resource is only complete if it +contains at least one contract offer and at least one representation with at least one artifact. +Otherwise, it will not be listed in the IDS self-description because there is no complete data offer.** + +Next, create a contract and one rule, that you add to the contract. The rule is the +object, that contains the usage policy as `value`. Since the IDS Usage Control Language is +rather complicated and it is not trivial to manually create a valid policy, endpoints are provided +to obtain example policies(`POST /api/examples/policy`) or to validate created and modified usage +policies (`POST /api/examples/validation`). + +By adding multiple rules to one contract offer, you are now able to add multiple usage policies to +one resource (e.g. the data usage can be logged and the data should be deleted at a given date). + +![Policy Endpoints](../../assets/images/swagger_policy.png) + +### Step 2: Add Local Data + +After we created created a contract offer and a resource offer and added the latter to a catalog, we +have to create a representation and add it to the resource offer. Then, we have to create an +artifact and add it to the representation. + +Within the artifact, you can specify whether you want to provide local data or remote data from an +external API (see [Step 3](#step-3-add-remote-data)). For local data, you would have to add the +following body to your `POST` request: + +```json +{ + "value": "Hello World" +} +``` + +Response body: + +````json +{ + "creationDate": "2021-05-17T19:40:37.375+0200", + "modificationDate": "2021-05-17T19:40:37.375+0200", + "remoteId": "genesis", + "title": "", + "numAccessed": 0, + "byteSize": 32, + "checkSum": 3110206735, + "additional": {}, + "_links": { + "self": { + "href": "https://localhost:8080/api/artifacts/911a0451-f93f-4850-9dc7-e1a70060b2d1" + }, + "data": { + "href": "https://localhost:8080/api/artifacts/911a0451-f93f-4850-9dc7-e1a70060b2d1/data" + }, + "representations": { + "href": "https://localhost:8080/api/artifacts/911a0451-f93f-4850-9dc7-e1a70060b2d1/representations{?page,size,sort}", + "templated": true + }, + "agreements": { + "href": "https://localhost:8080/api/artifacts/911a0451-f93f-4850-9dc7-e1a70060b2d1/agreements{?page,size,sort}", + "templated": true + } + } +} +```` + +In this case, the data will be stored within and loaded from the internal database. + +--- + +**Note**: The Dataspace Connector automatically calculates the bytesize and checksum. + +--- + +### Step 3: Add Remote Data + +For remote data, as in this example, it is possible to set the attributes `accessUrl`, `username`, +and `password` to define details for the data providing connector on how to retrieve the data from +connected backend systems or existing APIs. You do not need to specify whether you added remote or +local data. The Dataspace Connector automatically classifies an artifact as `remote` as soon as the +`accessUrl` property is filled. + +```json +{ + "accessUrl": "https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02" +} +``` + +Currently, the Dataspace Connector can natively establish a connection via http, https, and https +with basic authentication. To connect to other backends, take a look at how to integrate +routing frameworks as explained [here](../deployment/camel.md). + +In case an external REST API should be connected and this API usually expects query parameters from +the user, e.g. to retrieve the raw data in various formats, multiple artifacts can be created +for one representation, or multiple artifacts with one representation each. Each artifact can then +refer to one specified http request or database query with fix parameters. + +If you want to leave it up to the consumer which part of the data should be retrieved, a reference +to the OpenApi description of the connected REST Api as "endpointDocumentation" may be supplied. +This is useful, for example, if the connected API is a generic Linked Data platform. The consumer +can then pass request parameters when retrieving data, which are automatically resolved at the +Dataspace Connector to the provider backend. + +--- + +**Note**: While the connector has the ability to store data internally, it never duplicates data +connected by external systems into its internal memory. Instead, the data is only forwarded when a +request is received. In addition, the backend connection credentials are never passed on to another +connector, but are only used for internal data handling. + +--- + +### Step 4: Publish Resources at an IDS Broker (optional) + +For communicating with an IDS metadata broker, some endpoints are provided. +- `POST /api/ids/connector/update`: send a `ConnectorUpdateMessage` with the connector's + self-description as `payload` +- `POST /api/ids/connector/unavailable`: send a `ConnectorUnavailableMessage` to unregister the connector +- `POST /api/ids/resource/update`: update a previously created resource offer +- `POST /api/ids/resource/unavailable`: remove a previously registered resource offer +- `POST /api/ids/query`: send a `QueryMessage` with a SPARQL command (request parameter) as `payload` + +## Policy Enforcement + +When the data provider receives an `ArtifactRequestMessage` from an external connector, the +`ArtifactMessageHandler` iterates through all contracts of a resource offer and all of its rules +to check the policy pattern. If the pattern matches one of the following five, an appropriate policy +check is performed:`PROVIDE_ACCESS`, `PROHIBIT_ACCESS`, `USAGE_DURING_INTERVAL`, +`USAGE_UNTIL_DELETION`, or`CONNECTOR_RESTRICTED_USAGE`. + +Depending on the specified rules, the access permission will be set to true or false. If it is true, +the data provider returns the data. If not, it will respond with a `RejectionReason.NOT_AUTHORIZED`. + +--- + +**Note**: The contract negotiation is enabled by default. To disable it, have a look at the +[configurations](../deployment/configuration.md#ids-settings). + +--- + +## Resource Updates + +Currently, a data consumer cannot subscribe to a resource, and the Dataspace Connector as a data +provider does not automatically send `ResourceUpdateMessages` to every data consumer on metadata +changes. Instead, the data provider has to trigger update messages by using the respective endpoint. + +![Resource Update](../../assets/images/swagger_resource_updates.png) diff --git a/docs/pages/deployment.md b/docs/pages/deployment.md new file mode 100644 index 000000000..b34f54e60 --- /dev/null +++ b/docs/pages/deployment.md @@ -0,0 +1,24 @@ +--- +layout: default +title: Deployment +nav_order: 5 +description: "" +permalink: /Deployment +has_children: true +has_toc: true +--- + +# Deployment +{: .fs-9 } + +Here, you can find a detailed guide on how to configure and deploy the Dataspace Connector. +{: .fs-6 .fw-300 } + +--- + +This section gives information about the [configuration](deployment/configuration.md) and +[building](deployment/build.md) of the Dataspace Connector. Please make sure that you get familiar +with at least these two subpages. After that, you may want to have a look at the +[Communication Guide](communication.md) and see how to provide and consume data with our IDS +Connector implementation. Further information about database and logging adjustments, a +Kubernetes-specific deployment, or one with a data routing framework is provided on the other pages. diff --git a/docs/pages/deployment/build.md b/docs/pages/deployment/build.md new file mode 100644 index 000000000..f7fcdc1b0 --- /dev/null +++ b/docs/pages/deployment/build.md @@ -0,0 +1,137 @@ +--- +layout: default +title: Build +nav_order: 2 +description: "" +permalink: /Deployment/Build +parent: Deployment +--- + +# Build +{: .fs-9 } + +Take a look at the more detailed instructions to deploy the Dataspace Connector. +{: .fs-6 .fw-300 } + +--- + +If you want to set up the Dataspace Connector yourself, follow the instructions below. If you +encounter any problems, please have a look at the [FAQ](../faq.md). + +Clone the project: `git clone https://github.com/International-Data-Spaces-Association/DataspaceConnector.git` + +**For configurations, please have a look at [this section](configuration.md) first.** + +In the following, the deployment with Maven, Docker, and Kubernetes will be explained. + +## Maven + +If you want to build and run locally, ensure that at least Java 11 is installed. Then, follow these +steps: + +1. Execute `cd DataspaceConnector` and `mvn clean package`. +2. The connector can be started by running the Spring Boot Application. Therefore, navigate to + `/target` and run `java -jar dataspaceconnector-{VERSION}.jar`. + +If everything worked fine, the connector is available at +[https://localhost:8080/](https://localhost:8080/) and its API can be accessed at +[https://localhost:8080/api](https://localhost:8080/api). By default, the Dataspace Connector is +running with an h2 database. + +--- + +**Note**: After successfully building the project, the Javadocs as a static website can be found +at `/target/apidocs`. Open the `index.html` in a browser of your choice. + +--- + +The OpenApi documentation can be viewed at [https://localhost:8080/api/docs](https://localhost:8080/api/docs). +The JSON representation is available at [https://localhost:8080/v3/api-docs](https://localhost:8080/v3/api-docs). +The .yaml file can be downloaded at [https://localhost:8080/v3/api-docs.yaml](https://localhost:8080/v3/api-docs.yaml). + +The connector provides several endpoints for resource database handling and IDS messaging. Details +on how to interact with them can be found [here](../communication.md). + +* `Connector` and `Usage Control` provide information about the running connector and settings for + contract negotiation and policy enforcement behaviour. +* `IDS Messages` provides endpoints for requesting artifact (data) and descriptions (metadata) from + an external connector, and negotiate contracts. On top of that, endpoints for sending IDS + multipart messages to e.g. the IDS Metadata Broker are provided. +* All other sections provide endpoints for metadata and data management and entity relations + (`POST`, `PUT`, `GET`, and `DELETE`). + +Next to the endpoint implemented by the connector, an endpoint for handling incoming IDS messages +at `/api/ids/data` is provided by the IDS Framework. + +The database can be accessed via [https://localhost:8080/database](https://localhost:8080/database). + +### Profiles +The `pom.xml` provides three Maven profiles: `no-documentation`, `no-tests`, and +`release`. The first one skips the Javadocs generation, the second one skips the execution of +tests. The `release` profile shows all warnings and errors. To run a profile, please have a look at +[this guide](maven.apache.org/guides/introduction/introduction-to-profiles.html#details-on-profile-activation). + +### Plugins + +| Plugin | Command | Description | +|:-------|:--------|:------------| +| Checkstyle | mvn checkstyle:check | With this, a codestyle check is executed. | +| Statistics | mvn verify site | With this, project statistics are generated. | +| License | mvn license:format | With this, a license header is added to all projects files that are missing one. | + +## Docker + +If you want to deploy in Docker and build the Maven project with the `Dockerfile`, follow these +steps: + +### Option 1: Build and run Docker image +1. Navigate to `DataspaceConnector`. To build the image, run `docker build -t .` + (e.g. `docker build -t dataspaceconnector .`). +2. For running your image as a container, follow [these](https://docs.docker.com/get-started/part2/) + instructions: `docker run --publish 8080:8080 --detach --name dsc-container ` + +### Option 2: Using Docker Compose +1. The `docker-compose.yml` sets up the connector application and a PostgreSQL database. If + necessary, make your changes in the `connector.env` and `postgres.env`. Please find more details + about setting up different databases [here](database.md). +2. For starting the application, run `docker-compose up`. Have a look at the `docker-compose.yaml` + and make your own configurations if necessary. + +--- + +**Note**: Environment variables will overwrite the Spring Boot settings. + +--- + +## Kubernetes + +For a deployment with Kubernetes, have a look at [this](kubernetes.md) documentation. + +## Tests + +Tests will be executed automatically when running Maven commands `package`, `verify`, `install`, +`site`, or `deploy`. To run tests manually, execute the following commands in the root directory of +the project: +* Run all tests: `mvn test` +* Run specific test class: `mvn test -Dtest=[full class name]` +* Run a specific test case (single method): `mvn test -Dtest=[full class name]#[method name]` + + +### Open Telemetry with Jaeger + +To view tracing information, uncomment the dependency in the `pom.xml` at line 344. Then, a Docker +container has to be started: +``` +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14268:14268 \ + -p 14250:14250 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.22 +``` +The traces can then be accessed at [http://localhost:16686](http://localhost:16686). diff --git a/docs/pages/deployment/camel.md b/docs/pages/deployment/camel.md new file mode 100644 index 000000000..482442d7b --- /dev/null +++ b/docs/pages/deployment/camel.md @@ -0,0 +1,27 @@ +--- +layout: default +title: Routing +nav_order: 6 +description: "" +permalink: /Deployment/Routing +parent: Deployment +--- + +# Routing Frameworks +{: .fs-9 } + +Here, you can find instructions for using Camel with the Dataspace Connector. +{: .fs-6 .fw-300 } + +--- + +The communication between the Dataspace Connector and data apps can be achieved by using an +integration Framework like [Apache Camel](https://camel.apache.org/). This also provides the +possibility to use all kinds of different backends for resources registered in the Connector, as no +separate implementation has to be made for each possible protocol. To keep the Dataspace Connector +lightweight and modular, no integration framework will be integrated directly, but rather be +executed standalone in parallel to the Connector's core container. + +The repository [DSC Camel Instance](https://github.com/International-Data-Spaces-Association/DSC-Camel-Instance) +describes how to use Apache Camel together with the Dataspace Connector and gives examples for +connecting different backend types. diff --git a/docs/pages/deployment/configuration.md b/docs/pages/deployment/configuration.md new file mode 100644 index 000000000..4c8202509 --- /dev/null +++ b/docs/pages/deployment/configuration.md @@ -0,0 +1,347 @@ +--- +layout: default +title: Configuration +nav_order: 1 +description: "" +permalink: /Deployment/Configuration +parent: Deployment +--- + +# Configuration +{: .fs-9 } + +Customize the Dataspace Connector to fit your use case. +{: .fs-6 .fw-300 } + +--- + +If you want to set up the Dataspace Connector yourself, follow the instructions below. If you +encounter any problems, please have a look at the [FAQ](../faq.md). + +At first, clone the repository: +```commandline +git clone https://github.com/International-Data-Spaces-Association/DataspaceConnector.git +``` + +The resource folder `resources/conf` provides three important files that are loaded at application start: + +* `keystore-localhost.p12`: The provided keystore, on the one hand, is used as IDS certificate that + is loaded by the IDS Framework for requesting a valid Dynamic Attribute Token (DAT) from the Dynamic + Attribute Provisioning Service (DAPS). Each message to an IDS participant needs to be signed with a + valid DAT. On the other hand, it can be used as SSL certificate for TLS encryption. +* `truststore.p12`: The truststore is used by the IDS Framework for any HTTP/S communication. It + ensures the connection to trusted addresses. +* `config.json`: The configuration is used to set important properties for IDS message handling. + +## Step 1: Connector Properties + +When starting the application, the `config.json` will be scanned for important connector +information, e.g. its ID, address, contact information, or proxy settings. Please keep this +file up to date to your custom settings. In case you want to use the demo cert, you don't need +to change anything except the proxy settings. + +For outgoing requests, the connector needs information about an existing system proxy that needs to +be set in the `resources/conf/config.json`. + +```json +"ids:connectorProxy" : [ { + "@type" : "ids:Proxy", + "@id" : "https://w3id.org/idsa/autogen/proxy/548dc73a-ccfb-4039-9569-4b8e219b90bc", + "ids:proxyAuthentication" : { + "@type" : "ids:BasicAuthentication", + "@id" : "https://w3id.org/idsa/autogen/basicAuthentication/47e3cd59-d351-4f5b-99fc-561c94bad5e1" + }, + "ids:proxyURI" : { + "@id" : "http://host:port" + }, + "ids:noProxy" : [ { + "@id" : "https://localhost:8080/" + }, { + "@id" : "http://localhost:8080/" + } ] + } ] +``` + +Check if your system is running behind a proxy. If this is the case, specify the `ids:proxyURI` and +change `ids:noProxy` if necessary. Otherwise, delete the key `ids:connectorProxy` and its values. + +A full configuration example may look like this: +```json +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:ConfigurationModel", + "@id" : "https://w3id.org/idsa/autogen/configurationModel/7672b568-7878-4f62-8032-5c73de969414", + "ids:configurationModelLogLevel" : { + "@id" : "idsc:MINIMAL_LOGGING" + }, + "ids:connectorDeployMode" : { + "@id" : "idsc:TEST_DEPLOYMENT" + }, + "ids:connectorDescription" : { + "@type" : "ids:BaseConnector", + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea", + "ids:publicKey" : { + "@type" : "ids:PublicKey", + "@id" : "https://w3id.org/idsa/autogen/publicKey/78eb73a3-3a2a-4626-a0ff-631ab50a00f9", + "ids:keyType" : { + "@id" : "idsc:RSA" + }, + "ids:keyValue" : "[...]" + }, + "ids:description" : [ { + "@value" : "IDS Connector with static example resources hosted by the Fraunhofer ISST", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:version" : "1.0", + "ids:hasDefaultEndpoint" : { + "@type" : "ids:ConnectorEndpoint", + "@id" : "https://w3id.org/idsa/autogen/connectorEndpoint/e5e2ab04-633a-44b9-87d9-a097ae6da3cf", + "ids:accessURL" : { + "@id" : "https://localhost:8080/api/ids/data" + } + }, + "ids:outboundModelVersion" : "4.0.4", + "ids:inboundModelVersion" : [ "4.0.0", "4.0.4" ], + "ids:title" : [ { + "@value" : "Dataspace Connector", + "@type" : "http://www.w3.org/2001/XMLSchema#string" + } ], + "ids:securityProfile" : { + "@id" : "idsc:BASE_SECURITY_PROFILE" + }, + "ids:curator" : { + "@id" : "https://www.isst.fraunhofer.de/" + }, + "ids:maintainer" : { + "@id" : "https://www.isst.fraunhofer.de/" + } + }, + "ids:trustStore" : { + "@id" : "file:///conf/truststore.p12" + }, + "ids:connectorStatus" : { + "@id" : "idsc:CONNECTOR_ONLINE" + }, + "ids:keyStore" : { + "@id" : "file:///conf/keystore.p12" + }, + "ids:connectorProxy" : [ { + "@type" : "ids:Proxy", + "@id" : "https://w3id.org/idsa/autogen/proxy/548dc73a-ccfb-4039-9569-4b8e219b90bc", + "ids:proxyAuthentication" : { + "@type" : "ids:BasicAuthentication", + "@id" : "https://w3id.org/idsa/autogen/basicAuthentication/47e3cd59-d351-4f5b-99fc-561c94bad5e1" + }, + "ids:proxyURI" : { + "@id" : "http://proxy.dortmund.isst.fraunhofer.de:3128" + }, + "ids:noProxy" : [ { + "@id" : "https://localhost:8080/" + }, { + "@id" : "http://localhost:8080/" + }, { + "@id" : "https://localhost:8081/" + }, { + "@id" : "http://localhost:8081/" + } ] + } ] +} +``` + +--- + +**Note**: If you are not familiar with the IDS Information Model, the API provides an +endpoint `GET /api/examples/configuration` to print a filled in Java object as JSON-LD. Adapt +this to your needs, take the received string and place it in the `config.json`. + +If you want to connect to a running connector or any other system running at `https://`, +keep in mind that you need to add the keystore to your truststore. Otherwise, the communication +will fail. With the provided truststore, the Dataspace Connector accepts its own localhost +certificate, public certificates, and any IDS keystore that was provided by the Fraunhofer AISEC. + +--- + +## Step 2: IDS Certificate + +In the provided `config.json`, the `ids:connectorDeployMode` is set to `idsc:TEST_DEPLOYMENT`. This +allows to use the `keystore-localhost.p12` as an IDS certificate. For testing purpose, the existing +cert can be used, as on application start, the IDS Framework will not get a valid DAT from the DAPS +and for received messages, the sent DAT will not be checked. + +To turn on the DAT checking, you need to set the `ids:connectorDeployMode` to +`idsc:PRODUCTIVE_DEPLOYMENT`. For getting a trusted certificate, contact +[Gerd Brost](mailto:gerd.brost@aisec.fraunhofer.de). Add the keystore with the IDS certificate +inside to the `resources/conf` and change the filename at `ids:keyStore` accordingly. In addition, +set your connector id to uniquely identify your connector towards e.g. the IDS Metadata Broker: + +```json +"ids:connectorDescription" : { + "@type" : "ids:BaseConnector", + "@id" : "CONNECTOR_URL", +``` + +--- + +**Note**: The `TEST_DEPLOYMENT` mode and accepting a demo cert is for testing purposes only! +This mode is a **security risk** and cannot ensure that the connector is talking to a verified IDS +participant. Furthermore, messages from the Dataspace Connector without a valid IDS certificate +may not be accepted by other Connector implementations and will not be accepted by the IDS Metadata +Broker running in the IDS lab. + +--- + +## Step 3: General Settings (optional) + +The `application.properties` specifies several Spring Boot and IDS configurations. + +### Tomcat + +To define on which port the connector should be running, change `server.port={PORT}`. + +### OpenApi + +You can change Swagger properties by changing the following settings: + +```properties +springdoc.swagger-ui.path=/api/docs +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.disable-swagger-default-url=true +``` + +### SSL + +If you want to add your own SSL certificate, check the corresponding path. As the provided +certificate only supports the application running at `localhost`, you may replace this with your +IDS keystore, if you want to host the connector in a productive environment. + +```properties +server.ssl.enabled +server.ssl.key-store-type +server.ssl.key-store +server.ssl.key-store-password +server.ssl.key-alias +``` + +```properties +configuration.path +configuration.keyStorePassword +configuration.keyAlias +configuration.trustStorePassword +``` + +### Http Connections + +For customizing timeout settings for incoming and outgoing requests, you may customize the +following lines: + +```properties +http.timeout.connect=10000 +http.timeout.read=10000 +http.timeout.write=10000 +http.timeout.call=10000 +``` + +Not that either the call timeout is used, or the other three values. + +### Authentication + +The application uses Spring Security. Each endpoint behind `/**`, needs a user +authentication, except the open IDS endpoint at `/api/ids/data`. + +Have a look at the blocked endpoints in the `ConfigurationAdapter` class to add or change endpoints +yourself. In case you don't want to provide authentication for your backend maintenance, feel free +to remove the corresponding lines. + +For changing the default credentials, the properties are located at `spring.security.user.name` +and `spring.security.user.password`. + +### Database + +The Dataspace Connector uses Spring Data JPA to set up the database and manage interactions with it. +Spring Data JPA supports many well-known relational databases out of the box. Thus, the internal H2 +can be replaced by e.g. MySQL, PostgreSQL, or Oracle databases with minimal effort. + +To use another database for the Dataspace Connector, follow [these](database.md) steps. + +Settings are provided within the `application.properties` at: + +```properties +spring.datasource.url +spring.datasource.driverClassName +spring.datasource.username +spring.datasource.password + +spring.h2.console.enabled=false +spring.h2.console.path=/database +spring.h2.console.settings.web-allow-others=true +``` + +### Logging + +The Dataspace Connector provides multiple ways for logging and accessing information. Please find a +detailed description on how to set up static and runtime configurations [here](logging.md). + +Settings are provided within the `application.properties` at: + +```properties +management.endpoints.enabled-by-default=false +management.endpoints.web.exposure.include=logfile, loggers +management.endpoint.loggers.enabled=true +management.endpoint.logfile.enabled=true +management.endpoint.logfile.external-file=./log/dataspaceconnector.log +``` + +Http tracing is disabled by default: `httptrace.enabled=false`. + +### Jaeger + +If your want to access open telemetry, have a look at [this guide](build.md#docker). You can +customize the deployment with these lines: + +```properties +opentracing.jaeger.udp-sender.host=localhost +opentracing.jaeger.udp-sender.port=6831 +opentracing.jaeger.log-spans=true +``` + +### IDS Settings + +URLs of the DAPS for IDS identity management and the Clearing House for contract agreement and data +usage logging can be changed within the following lines: + +```properties +daps.token.url=https://daps.aisec.fraunhofer.de +daps.key.url=https://daps.aisec.fraunhofer.de/v2/.well-known/jwks.json +clearing.house.url=https://ch-ids.aisec.fraunhofer.de/logs/messages/ +``` + +If you leave the Clearing House address blank, the connector will ignore sending IDS messages to it. + +Also, for usage control, some settings are provided: + +```properties +policy.negotiation=true +policy.allow-unsupported-patterns=false +policy.framework=INTERNAL +``` + +Contract negotiation is enabled by default. This forces other Connectors to refer to a valid +contract agreement when requesting data access via an `ArtifactRequestMessage`. If you want to +deactivate the policy negotiation, as data provider or data consumer, use the following endpoints +or the corresponding line within the `application.properties`. + +![Policy Negotiation Settings](../../assets/images/negotiation_settings.png) + +Note that the Dataspace Connector is able to received resources with usage policies that follow +the IDS policy language but not one of the supported patterns. As, by default, the policy check on +the data consumer side would not allow accessing data whose policies cannot be enforced, you are +able to ignore unsupported patterns with setting the boolean at the endpoint +`/api/configuration/pattern` or the property `policy.allow-unsupported-patterns` in the +`application.properties` to `true`. As a data consumer, you are bound to concluded contract +agreements that are technically mapped to IDS usage policies. Therefore, you have to ensure, that +your backend applications technically enforce the usage policies instead. + +![Unsupported Pattern Settings](../../assets/images/pattern_settings.png) diff --git a/docs/pages/deployment/database.md b/docs/pages/deployment/database.md new file mode 100644 index 000000000..7feb27850 --- /dev/null +++ b/docs/pages/deployment/database.md @@ -0,0 +1,131 @@ +--- +layout: default +title: Database +nav_order: 3 +description: "" +permalink: /Deployment/Database +parent: Deployment +--- + +# Database Configuration +{: .fs-9 } + +On this page, you can find some more details on how to replace the built-in database. +{: .fs-6 .fw-300 } + +--- + +The Dataspace Connector uses Spring Data JPA to set up the database and manage interactions with it. +Spring Data JPA supports many well-known relational databases out of the box. Thus, the internal H2 +can be replaced by e.g. MySQL, PostgreSQL, or Oracle databases with minimal effort. + +To use another database for the Connector, follow these steps: + +1. Add the dependency for your chosen database to the Connector's `pom.xml` (contains required JDBC + driver). +2. Adjust the following parameters in `src/main/resources/application.properties`: + + ```properties + spring.datasource.url + spring.datasource.platform + spring.datasource.driver-class-name + spring.datasource.username + spring.datasource.password + spring.jpa.database-platform + ``` + +3. Check what types for large objects your chosen database supports. In some entity classes, some + fields reference another object, and the column definition for the respective fields is set to + the value `@LOB`. This type works for H2 but might not be + supported in your chosen database. In that case, set the column definition to a supported value. + Spring will take care of the rest. The next time you start the Connector, it will use the newly + configured database. If the tables have not yet been created in the new database, remember to set + the property `spring.jpa.hibernate.ddl-auto` to the value `create` for the first application + start. Spring will generate tables for all entities. For consecutive application starts, the + property can be set to `update`, if data should not be lost. + +Keep in mind that when using an external database, the specified database is required to be running +and reachable when starting the Connector, otherwise it will fail. So you will either have to run a +database instance locally or, in case of using Docker, add a database container to your setup. +Docker images of common databases are freely available. + + +## Example + +Hereinafter, an example on how to use a PostgreSQL database with the Connector will be given. + +### Setup PostgreSQL + +The following steps are the same for other databases, just with different values. +Adjust datasource properties in `application.properties`: + +```properties +spring.datasource.url = jdbc:postgresql://[postgres-host]:5432/[database-name] +spring.datasource.platform = postgres +spring.datasource.driver-class-name = org.postgresql.Driver +spring.datasource.username = [username] +spring.datasource.password = [password] +spring.jpa.database-platform = org.hibernate.dialect.PostgreSQLDialect +``` + +### Execute Docker + +To run the connector with `docker-compose.yaml`, a PostgreSQL container has to be added to the setup. +Therefore, add the following service to the `docker-compose.yaml`: + +```yml + postgres: + image: postgres + ports: + - "5432:5432" + env_file: + - ./postgres.env +``` + +In the file `postgres-params.env`, specify the database name, username, and password for the +PostgreSQL container: + +```properties +POSTGRES_USER=connector +POSTGRES_PASSWORD=12345 +POSTGRES_DB=connectordb +``` + +The last step is to set the Connector's properties so that it uses the database provided in the +container. To achieve this, add an env file to the Connector service in `docker-compose.yaml`: + +```yml + env_file: + - ./connector.env +``` + +In this file, specify the following parameters: + +```properties +SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/connectordb +SPRING_DATASOURCE_USERNAME=connector +SPRING_DATASOURCE_PASSWORD=12345 +``` + +To ensure that the Connector container waits until the Postgres container is ready, before it tries +to connect, also add this to the Connector service in ``docker-compose.yaml``: + +```yml + depends_on: + - postgres +``` + +By default, all data in the Postgres container will be lost when running `docker-compose down`. If +you want to keep your data persisted across restarts, add a persistent volume in the +`docker-compose.yaml`: + +```yml + services: + postgres: + image: postgres + volumes: + - connector-data:/var/lib/postgresql/data + + volumes: + connector-data: {} +``` diff --git a/docs/pages/deployment/kubernetes.md b/docs/pages/deployment/kubernetes.md new file mode 100644 index 000000000..12cf3009d --- /dev/null +++ b/docs/pages/deployment/kubernetes.md @@ -0,0 +1,163 @@ +--- +layout: default +title: Kubernetes +nav_order: 4 +description: "" +permalink: /Deployment/Kubernetes +parent: Deployment +--- + +# Kubernetes +{: .fs-9 } + +Example and description for deploying the Dataspace Connector in Kubernetes. +{: .fs-6 .fw-300 } + +--- + +In addition to the instructions below, the IDSA community provides a free +[GitHub repository](https://github.com/International-Data-Spaces-Association/IDS-Deployment-Examples) +with sample deployments. These include not only the Dataspace Connector or a deployment together +with ConfigManager and GUI, but also some with other IDS components. The goal is to provide an easy +entry into the whole IDS ecosystem. Feel free to have a look at the files or contribute with your +own examples. + +## PostgreSQL Deployment + +The Dataspace Connector uses an internal H2 database per default. When deploying the Dataspace +Connector in a Kubernetes cluster, this will lead to inconsistencies as soon as there are multiple +connector replicas. Therefore, an external database should be used. + +Execute the 3 following commands in the given order in the root directory of the project to start a +PostgreSQL database that the Dataspace Connector can use: + +```commandline +kubectl create -f postgres-configmap.yaml +kubectl apply -f postgres-deployment.yaml +kubectl expose -f postgres-service.yaml +``` + +Now, a PostgreSQL instance is running and accessible by other services in the cluster using the +`service name` (*postgres*) and the `service port` (*5432*). + +## Connector Deployment + +### Secret + +Kubernetes uses secrets for storing sensitive data like passwords or certificates. The Dataspace +Connector uses an IDS certificate, an SSL certificate, and a truststore, all of which should not be +exposed to the outside or be easily accessible. Thus, these files should be stored in a secret, +which can then be mounted to a specified directory of the Dataspace Connector pods. + +If you want to use an external configuration (`config.json`), you can add it to the secret as well +or create a second secret containing the configuration. + +--- + +**Note:** +The example deployment expects the configuration to be in the same secret as the certificates. + +--- + +To create a secret, put all files it should contain in a directory and execute the following +command: + +```commandline +kubectl create secret generic dataspace-connector-certs --from-file=path/to/certs/directory +``` + +If you create a second secret, be sure to define and mount that in `deployment.yaml` and, if +necessary, change the property `CONFIGURATION_PATH` to point to the config file in the specified +mounted directory. If you don't want to use an external configuration file, delete the property +`CONFIGURATION_PATH` from `deployment.yaml`. + +### Deployment + +A deployment tells Kubernetes how to set up an application. That includes e.g. the image to use, +environment variables, and resource requirements (memory, CPU). The `deployment.yaml` gives an +example on how to configure the Dataspace Connector deployment. + +##### Configuration + +Settings from `application.properties`: At `env`, all properties presented in or being available +for Spring's `application.properties` can be added or overridden. In this example, the Dataspace +Connector uses a PostgreSQL database. If you want to use another database, the corresponding +database values can be changed here. You can also set the path to the configuration file in case of +supplying an external configuration file or specify the SSL certificate to use. + +Image: The registry and specific images to be used for the deployment can be configured at +`image`. In the example, a local docker registry running on port 5000 is used. For the example, the +[local registry](https://docs.docker.com/registry/deploying/) has to be running and contain the +Dataspace Connector image. Alternatively, you can change the registry and image name in the +deployment file or omit the registry to use a locally built image. + +If you want to use a private registry that requires credentials, first create a docker-registry +secret: + +```commandline +kubectl create secret docker-registry registry-credentials --docker-server=[registry-server] + --docker-username=[username] --docker-password=[password] --docker-email=[email] +``` + +You can then tell Kubernetes to use this secret when pulling the image by adding the following lines +to `deployment.yaml` at `spec` with `imagePullSecrets` being on the same level as `containers` and +`volumes`: + +```yaml +imagePullSecrets: + - name: registry-credentials +``` + +Mounted Directory: In the example, the secret containing the key- and truststores as well as +the configuration file is mounted to the pod at `/connector-certs`. For the connector to find the +certificates, the paths in the `config.json` have to be set to `/connector-certs/[certificate name]`. +Alternatively, you can change the mount path of the secret at `volumeMounts`. + +##### Starting the deployment + +To start the deployment, execute the following command: + +```commandline +kubectl apply -f deployment.yaml +``` + +### Service + +A service is an abstraction for a set of pods and defines how the set can be accessed. In a service, +you can define e.g. what port your application should expose and under what name other services can +reach it. The `service.yaml` gives an example on how to define a service for the Dataspace +Connector. There are different types of services. The service type defaults to `ClusterIP` if not +specified, as is the case here, meaning that it can be accessed by other services in the cluster +using the service's `name` and `port`. To make the service accessible from outside the cluster, +either choose a different +[service type](https://kubernetes.io/docs/concepts/services-networking/service/) +or create an [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/). + +To start the service as type `LoadBalancer` (reachable from inside and outside the cluster), execute + +```commandline +kubectl expose -f service.yaml --type=LoadBalancer +``` + +Afterwards, you can find the IP of the Master node by executing + +```commandline +kubectl cluster-info +``` + +and the NodePort the service is running on by executing + +```commandline +kubectl describe service dataspace-connector +``` + +With this IP and port you can now reach the connector using e.g. cURL or an HTTP client. + +--- + +**Note:** +This example was tested using Minikube. Depending on the Kubernetes distribution you +use, `kubectl` might have to be replaced with another command. +* When using OpenShift, replace `kubectl` with `oc`. +* When using MicroK8s, replace `kubectl` with `microk8s kubectl` +* When using Minikube, replace `kubectl` with `minikube kubectl --` diff --git a/docs/pages/deployment/logging.md b/docs/pages/deployment/logging.md new file mode 100644 index 000000000..86e8d214f --- /dev/null +++ b/docs/pages/deployment/logging.md @@ -0,0 +1,92 @@ +--- +layout: default +title: Logging +nav_order: 5 +description: "" +permalink: /Deployment/Logging +parent: Deployment +--- + +# Logging +{: .fs-9 } + +Here, you can find a detailed description on how to use built-in logging functionality. +{: .fs-6 .fw-300 } + +--- + +The Dataspace Connector provides multiple ways for logging and accessing information. + +## Static Configuration + +You may configure logging setting in the `log4j2.xml` at `src/main/resources`. There, you will find +the different loggers and the target outputs used within the Dataspace Connector. + +To change the logging level of the Dataspace Connector, modify the attribute `level` of the logger +named `io.dataspaceconnector`. The different values of logging level can be found +[here](https://logging.apache.org/log4j/2.x/manual/configuration.html#SystemProperties). + +```xml + + + +``` + +The `AppenderRef` of the logger controls the output of the log. Add or remove elements of type +`AppenderRef` to add additional outputs or remove existing ones. + +The Dataspace Connector offers preconfigured appenders. For logging to console use `ConsoleAppender` +or to file use `RollingFile` as values for the `ref` attribute. + +```xml + + + + +``` + +Note that the root logger is already logging to file by default. This logger already contains all +logs of the `io.dataspaceconnector` logger. + +To add additional logging outputs or change the logging format consult +[here](https://logging.apache.org/log4j/2.x/manual/appenders.html) or for more information +see [here](https://logging.apache.org/log4j/2.x/manual/configuration.html#XML). + +## Runtime Configuration +The Dataspace Connector allows the modification of logging levels at runtime. To enable this +feature, you will need to locate `application.properties` under `src/main/resources`. + +Enable or add the following lines: + +```properties +management.endpoints.web.exposure.include=loggers +management.endpoint.loggers.enabled=true +``` + +A list of all available loggers and their current logging level will be exposed under +`/actuator/loggers`. + +To change the logging level at runtime, you will need to perform a `POST` request against the +logger. Here is an example using curl: + +```commandline +curl -i -k -X POST -H 'Content-Type: application/json' + -d '{"configuredLevel": "OFF"}' https://localhost:8080/actuator/loggers/io.dataspaceconnector +``` + +## Remote Access +To get remote access to the log file, find the `application.properties` at `src/main/resources`. +By default, the Dataspace Connector disables all optional endpoints. + +Enable or add the following lines: + +```properties +management.endpoints.web.exposure.include=logfile +management.endpoint.logfile.enabled=true +management.endpoint.logfile.external-file=./log/dataspaceconnector.log +``` + +The logfile will be available by performing a pull request on `/actuator/logfile`. + +For more information, see +[here](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html). diff --git a/docs/pages/documentation.md b/docs/pages/documentation.md new file mode 100644 index 000000000..696c0450d --- /dev/null +++ b/docs/pages/documentation.md @@ -0,0 +1,21 @@ +--- +layout: default +title: Documentation +nav_order: 7 +description: "" +permalink: /Documentation +has_children: true +has_toc: true +--- + +# Software Documentation +{: .fs-9 } + +For the developers, some software documentation is provided. +{: .fs-6 .fw-300 } + +--- + +You might want to know more about the data model and the REST API or get an insight into the +architecture of the Dataspace Connector. Have a look at the pages in this section. The documentation +will be updated regularly. diff --git a/docs/pages/documentation/architecture.md b/docs/pages/documentation/architecture.md new file mode 100644 index 000000000..b1bb94045 --- /dev/null +++ b/docs/pages/documentation/architecture.md @@ -0,0 +1,64 @@ +--- +layout: default +title: Architecture +nav_order: 1 +description: "" +permalink: /Documentation/Architecture +parent: Documentation +--- + +# Architecture +{: .fs-9 } + +Have a look at the Dataspace Connector's architecture. +{: .fs-6 .fw-300 } + +--- + +The following illustration visualizes the interaction of the Dataspace Connector, the +[IDS Framework](https://github.com/FraunhoferISST/IDS-Connector-Framework), the +[Configuration Manager](https://github.com/FraunhoferISST/IDS-ConfigurationManager), and it's +[GUI](https://github.com/International-Data-Spaces-Association/IDS-ConfigurationManager-UI). +All components have a defined API that allows individual components to be removed or replaced. The +Dataspace Connector can be deployed standalone and can be connected to existing backend systems. +Configuration Manager and GUI facilitate the operation and configuration of the connector. If +desired, the Dataspace Connector may be replaced by another connector implementation, either +integrating the IDS Framework or not. + +For the use of data apps, the data exchange between these and the Dataspace Connector takes place +by using an integration framework, such as Apache Camel, via the defined APIs. +To keep the Dataspace Connector as lightweight and modular as possible, frameworks like Apache Camel +will not be directly integrated into the core container. Instead, they are executed in parallel +and can thus be easily replaced by other frameworks, e.g. Apache Airflow. +The Configuration Manager defines and manages the routes at connector runtime and can thereby control +the data flow between different systems and apps. A monitoring system, e.g. Yacht (next to or inside +the Configuration Manager), helps to monitor and manage the loads of the individual components, +download images, and start or stop pods/containers. + +![Connector Setup](../../assets/images/dsc_architecture.png) + +All functionalities and architectural decisions aim at providing a maintainable and easily +extensible software that encapsulates the IDS information model from connected systems. + +The basic communication logic is inside the IDS Framework, whereas the business logic is in focus +of the Dataspace Connector itself. + +**Is the Dataspace Connector the Execution Core of an IDS Connector?** + +Referring to the IDS Reference Architecture of a Connector, it can be stated that the Dataspace +Connector is not a classic Execution Core. It can be extended by the ConfigManager, but can also be +used without it. That is important for many smaller use cases and also the reason why the Connector +is e.g. not divided into control and data plane. On top of that, it does not contain a message +bus/router. + +**Why is Camel separate?** + +Not every use case needs Data Apps, so the Dataspace Connector should be designed lightweight. +The idea is to enable different message bus systems, like Apache Airflow, Argo, Kafka, etc. + +## Network Architecture +The Dataspace Connector will support a segmented network. Every running container will be associated +to a different network zone by providing its own virtual network stack. The Connector as the core +container will have root rights and be able to manage network and firewall configurations for all +separated containers and their networks. As root namespace, it provides an external IP and can be +reached from an external network. Details can be found [here](../roadmap/concept.md). diff --git a/docs/pages/documentation/data-model.md b/docs/pages/documentation/data-model.md new file mode 100644 index 000000000..bc5aca65b --- /dev/null +++ b/docs/pages/documentation/data-model.md @@ -0,0 +1,232 @@ +--- +layout: default +title: Data Model +nav_order: 2 +description: "" +permalink: /Documentation/DataModel +parent: Documentation +--- + +# Data Model +{: .fs-9 } + +An explanation of the Dataspace Connector's data model. +{: .fs-6 .fw-300 } + +--- + +The data model of the Dataspace Connector is based on the structure of the IDS Infomodel. On the top +level, metadata of a data object is called a `resource`. This includes e.g. the title, a description, +license information, and a list of `contract offers`. Resources are organized by `catalogs`. An IDS +Connector can have multiple catalogs. A resource also has a list of `representations` that describes +the offered data in more detail. This includes e.g. the data type. Below the representation, there +is the `artifact`. This has a 1:1 relation to the raw data and describes e.g. checksum and +bytesize. An artifact then has a reference to `contract agreements`, which describe the agreed usage +between data provider and data consumer. Contract offers as `contracts` can contain multiple rules. +Each rule may represent one IDS Usage Control Pattern as described [here](usage-control.md). + +![Data Model Overview](../../assets/images/data_model.png) + +Furthermore, the Connector distinguishes between offered and requested resources. If data is offered, +it is called an offered resource. The interfaces allow these resources to be modified and deleted +without any restrictions. If data is requested by a consumer, it is stored as requested resource +after being received. The interfaces for requested resources and contract agreements only allow +interactions and manipulations that do not affect the technical assurance of data sovereignty. Also, +requested resources cannot be registered as such with an IDS broker. Instead, an explicit offer must +be created. + +By default, the Dataspace Connector implements a subset of the IDS Infomodel. Some attributes can be +defined via the interfaces, while others are gathered automatically. Each input is checked and empty +inputs have a predefined default value. For a domain specific usage of the Dataspace Connector, each +database entity has an additional "attributes" field, which allows storing custom key value pairs +into the database. + +An overview of all entities and its attributes are listed in the table below. Between all parent and +child entities n to m relations are provided. + +| Entity | Provided Attributes | Automated Attributes | Relations | +|:-------|:-----------|:---------------------|:---------| +| catalog | title, description | - | n offers, n requests| +| offer | title, description, keywords, publisher, sovereign, language, license, endpointDocumentation | version| n representations, n contracts, n catalogs | +| request | title, description, keywords, publisher, sovereign, language, license, endpointDocumentation | version| n representations, n contracts, n catalogs | +| representation | title, mediaType, language, standard | remoteId, | n artifacts, n offers/requests | +| artifact | title, accessUrl, username, password | remoteId, remoteAddress, numAccessed, automatedDownload, byteSize, checkSum | n representations, n agreements | +| contract | consumer, provider, title, start, end | remoteId| n rules, n offers/requests | +| rule | title, value | remoteId | n contracts | +| agreement | - | remoteId, confirmed, archived, value | n artifacts | + +Mapping between the Dataspace Connector's data model and the IDS Infomodel takes place for any IDS +communication. Then a catalog with offered resources is built from all related objects. + +--- + +**Note**: An offered resource is only complete if it contains at least one contract offer and at +least one representation with at least one artifact. Otherwise, it will not be listed in the IDS +self-description because there is no complete data offer. + +--- + +An example IDS catalog could look like that: + +```json +{ + "@context":{ + "ids":"https://w3id.org/idsa/core/", + "idsc":"https://w3id.org/idsa/code/" + }, + "@type":"ids:ResourceCatalog", + "@id":"https://localhost:8080/api/catalogs/9ca7ed44-4321-4bd7-8445-b771b5dc3904", + "ids:offeredResource":[ + { + "@type":"ids:Resource", + "@id":"https://localhost:8080/api/offers/726b8938-9058-4cff-8aaf-47c1146ce98c", + "ids:language":[ + { + "@id":"idsc:EN" + } + ], + "ids:created":{ + "@value":"2021-05-17T15:18:52.363Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:version":"1", + "ids:description":[ + { + "@value":"", + "@language":"" + } + ], + "ids:title":[ + { + "@value":"", + "@language":"" + } + ], + "ids:sovereign":{ + "@id":"" + }, + "ids:publisher":{ + "@id":"" + }, + "ids:representation":[ + { + "@type":"ids:Representation", + "@id":"https://localhost:8080/api/representations/9f1fc799-3bbf-4613-9e91-4d9d2f92fa97", + "ids:instance":[ + { + "@type":"ids:Artifact", + "@id":"https://localhost:8080/api/artifacts/688c3e91-461c-4f1f-98e2-e2f7971e8cba", + "ids:fileName":"", + "ids:creationDate":{ + "@value":"2021-05-17T15:18:52.749Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:byteSize":32, + "ids:checkSum":"3110206735" + } + ], + "ids:language":{ + "@id":"idsc:EN" + }, + "ids:created":{ + "@value":"2021-05-17T15:18:52.566Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:mediaType":{ + "@type":"ids:IANAMediaType", + "@id":"https://w3id.org/idsa/autogen/iANAMediaType/7a5c145d-c6e8-48ff-a628-ac99f38f56ec", + "ids:filenameExtension":"" + }, + "ids:modified":{ + "@value":"2021-05-17T15:18:52.566Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:representationStandard":{ + "@id":"" + } + } + ], + "ids:resourceEndpoint":[ + { + "@type":"ids:ConnectorEndpoint", + "@id":"https://w3id.org/idsa/autogen/connectorEndpoint/84112ee0-01de-4284-879f-759ab94ade94", + "ids:endpointDocumentation":[ + { + "@id":"" + } + ], + "ids:accessURL":{ + "@id":"https://localhost:8080/api/offers/726b8938-9058-4cff-8aaf-47c1146ce98c" + } + } + ], + "ids:contractOffer":[ + { + "@type":"ids:ContractOffer", + "@id":"https://localhost:8080/api/contracts/747e6404-178b-4910-b35e-749b0a8057de", + "ids:permission":[ + { + "@type":"ids:Permission", + "@id":"https://localhost:8080/api/rules/0c9e86dc-3453-461c-bdfc-e1fbb3e8ffe5", + "ids:description":[ + { + "@value":"provide-access", + "@type":"http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title":[ + { + "@value":"Example Usage Policy", + "@type":"http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action":[ + { + "@id":"idsc:USE" + } + ] + } + ], + "ids:provider":{ + "@id":"" + }, + "ids:consumer":{ + "@id":"" + }, + "ids:contractEnd":{ + "@value":"2021-05-17T15:18:52.906Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:obligation":[ + + ], + "ids:prohibition":[ + + ], + "ids:contractDate":{ + "@value":"2021-05-17T17:19:53.749+02:00", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:contractStart":{ + "@value":"2021-05-17T15:18:52.906Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + } + } + ], + "ids:keyword":[ + { + "@value":"DSC", + "@language":"" + } + ], + "ids:standardLicense":{ + "@id":"" + }, + "ids:modified":{ + "@value":"2021-05-17T15:18:52.363Z", + "@type":"http://www.w3.org/2001/XMLSchema#dateTimeStamp" + } + } + ] +} +``` diff --git a/docs/pages/documentation/messages.md b/docs/pages/documentation/messages.md new file mode 100644 index 000000000..d32f1ba6f --- /dev/null +++ b/docs/pages/documentation/messages.md @@ -0,0 +1,240 @@ +--- +layout: default +title: Messages +nav_order: 4 +description: "" +permalink: /Documentation/Messages +parent: Documentation +--- + +# IDS Messages +{: .fs-9 } + +See what IDS message types can be sent and received. +{: .fs-6 .fw-300 } + +--- + +## Message Types + +IDS messages and their content are defined [here](http://htmlpreview.github.io/?https://github.com/IndustrialDataSpace/InformationModel/blob/feature/message_taxonomy_description/model/communication/Message_Description.htm). +The table below lists the supported message types. Thereby, it is to be distinguished whether the +Connector provides functionality for sending messages as request or response, or for processing +incoming messages. + +| IDS Message Type | Outgoing | Incoming | Description | +|:------------------------------------|:------------------:|:--------:|:-------------------------| +| ArtifactRequestMessage | request | x | message asking for retrieving a specified artifact | +| DescriptionRequestMessage | request | x | message requesting metadata (If no URI is supplied via the ids:requestedElement field, this messages is treated like a self-description request.) | +| ContractRequestMessage | request | x | message containing a contract offer | +| ArtifactResponseMessage | response | | message that contains the artifact's data in the payload | +| DescriptionResponseMessage | response | | message containing the metadata of a requested object | +| ContractAgreementMessage | request + response | | message containing a contract agreement | +| ContractRejectionMessage | response | | message indicating rejection of a contract | +| RejectionMessage | response | | message that notifies the issuer that processing the request message has failed | +| NotificationMessage | request | x | message is informative and no response is expected | +| LogMessage | request | | message that is used to transfer logs e.g. to the clearing house | +| MessageProcessedNotificationMessage | response | | message that notifies whether a message has been received and successfully processed | +| ConnectorUpdateMessage | request | | message notifying the recipient(s) about the availability and current configuration of a connector | +| ConnectorUnavailableMessage | request | | message indicating that a specific connector is unavailable | +| ResourceUpdateMessage | request | x | message indicating the availability and current description of a specific resource | +| ResourceUnavailableMessage | request | | message indicating that a specific resource is unavailable | +| QueryMessage | request | | message intended to be consumed by specific components | + +`request` = initially send this kind of IDS message, `response` = response with this IDS message + +## Sequences + +![Automated IDS Messaging Sequence](../../assets/images/message_sequence_1.png) + +![Automated Data Updates](../../assets/images/message_sequence_2.png) + +## Examples + +### Description Request: Self-description + +Request Message: +``` +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:DescriptionRequestMessage", + "@id" : "https://w3id.org/idsa/autogen/descriptionRequestMessage/cc5afba5-db62-4c68-9858-6fd27c9f521b", + "ids:senderAgent" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:issuerConnector" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:issued" : { + "@value" : "2020-10-13T13:55:54.345+02:00", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:modelVersion" : "4.0.0", + "ids:securityToken" : { + "@type" : "ids:DynamicAttributeToken", + "@id" : "https://w3id.org/idsa/autogen/dynamicAttributeToken/21b0ba17-dfb3-42f2-b7d0-ece4debfa4af", + "ids:tokenValue" : "...", + "ids:tokenFormat" : { + "@id" : "idsc:JWT" + } + }, + "ids:recipientConnector" : [ { + "@id" : "https://localhost:8080/api/ids/data" + } ] +} +``` + +Response Message: +``` +-- +Content-Disposition: form-data; name="header" +Content-Type: text/plain;charset=UTF-8 +Content-Length: 1267 + +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:DescriptionResponseMessage", + "@id" : "https://w3id.org/idsa/autogen/descriptionResponseMessage/d4d67aaa-5e3f-479c-a144-6ada7ced91ad", + "ids:modelVersion" : "4.0.0", + "ids:issued" : { + "@value" : "2021-01-24T17:47:55.143+01:00", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:issuerConnector" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:senderAgent" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:securityToken" : { + "@type" : "ids:DynamicAttributeToken", + "@id" : "https://w3id.org/idsa/autogen/dynamicAttributeToken/3fa5130c-6983-4f88-b496-c54f686764c1", + "ids:tokenValue" : "...", + "ids:tokenFormat" : { + "@id" : "idsc:JWT" + } + }, + "ids:recipientConnector" : [ { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + } ], + "ids:correlationMessage" : { + "@id" : "https://w3id.org/idsa/autogen/descriptionRequestMessage/d5f9dc10-94a3-4d92-a6ba-e4eea14fd463" + } +} +-- +Content-Disposition: form-data; name="payload" +Content-Type: text/plain;charset=UTF-8 +Content-Length: 4051 + +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:BaseConnector", + ... +} +-- +``` + +### Description Request: Metadata + +Request Message: +``` +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:DescriptionRequestMessage", + "@id" : "https://w3id.org/idsa/autogen/descriptionRequestMessage/bb1384e2-bd4b-4c28-b18a-3c95d476ef5a", + "ids:senderAgent" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:issuerConnector" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:issued" : { + "@value" : "2020-10-13T13:54:29.041+02:00", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:modelVersion" : "4.0.0", + "ids:securityToken" : { + "@type" : "ids:DynamicAttributeToken", + "@id" : "https://w3id.org/idsa/autogen/dynamicAttributeToken/12977a5e-8014-4457-b2a1-ded777e9730e", + "ids:tokenValue" : "...", + "ids:tokenFormat" : { + "@id" : "idsc:JWT" + } + }, + "ids:recipientConnector" : [ { + "@id" : "https://localhost:8080/api/ids/data" + } ], + "ids:requestedElement" : { + "@id" : "https://w3id.org/idsa/autogen/resource/a4212311-86e4-40b3-ace3-ef29cd687cf9" + } +} +``` + +Response Message: + +``` +-- +Content-Disposition: form-data; name="header" +Content-Type: text/plain;charset=UTF-8 +Content-Length: 1267 + +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:DescriptionResponseMessage", + "@id" : "https://w3id.org/idsa/autogen/descriptionResponseMessage/325d6118-b5aa-4364-a8c0-4b660715aeb9", + "ids:modelVersion" : "4.0.0", + "ids:issued" : { + "@value" : "2021-01-24T17:52:22.760+01:00", + "@type" : "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "ids:issuerConnector" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:senderAgent" : { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + }, + "ids:securityToken" : { + "@type" : "ids:DynamicAttributeToken", + "@id" : "https://w3id.org/idsa/autogen/dynamicAttributeToken/8ff9b176-22fd-4fc3-a2ff-0d324bb6c515", + "ids:tokenValue" : "...", + "ids:tokenFormat" : { + "@id" : "idsc:JWT" + } + }, + "ids:recipientConnector" : [ { + "@id" : "https://w3id.org/idsa/autogen/baseConnector/7b934432-a85e-41c5-9f65-669219dde4ea" + } ], + "ids:correlationMessage" : { + "@id" : "https://w3id.org/idsa/autogen/descriptionRequestMessage/0c57495a-c31a-461c-a372-6f5d1412f0bd" + } +} +-- +Content-Disposition: form-data; name="payload" +Content-Type: text/plain;charset=UTF-8 +Content-Length: 2527 + +{ + "@context" : { + "ids" : "https://w3id.org/idsa/core/", + "idsc" : "https://w3id.org/idsa/code/" + }, + "@type" : "ids:Resource", + ... +} +-- +``` diff --git a/docs/pages/documentation/rest-api.md b/docs/pages/documentation/rest-api.md new file mode 100644 index 000000000..e48f942bc --- /dev/null +++ b/docs/pages/documentation/rest-api.md @@ -0,0 +1,68 @@ +--- +layout: default +title: REST API +nav_order: 3 +description: "" +permalink: /Documentation/RestApi +parent: Documentation +--- + +# REST API +{: .fs-9 } + +Get to know the Dataspace Connector's REST API to automated resource handling. +{: .fs-6 .fw-300 } + +--- + +If you haven't already checked it out, please first take a look at the Dataspace Connector data +model [here](data-model.md). As mentioned there, the data model of the Connector is very modular. +Relations between objects are predefined and via the REST API, a data offer can thus be created very +dynamically. Individual objects can be detached from each other, attached to other objects, and +modified at any time. + +Overview of all available endpoints reduced to generic endpoints: + +| Method | Endpoint | Usage | Returns | +| :----- | :------------- | :-------------| :-----| +| GET | / | Get the connector | connector | +| | +| POST | /Ts | Create a T | - | +| GET | /Ts | Get a list of all T | Ts | +| GET | /Ts/{​​​​id}​​​​ | Get a T | T | +| PUT | /Ts/{​​​​id}​​​​ | Change a T's details | - | +| DELETE | /Ts/{​​​​id}​​​​ | Remove a T | - | +| GET | /Ts/{​​​​id}​​​​/Xs | Get a T's Xs | Xs | +| POST | /Ts/{​​​​id}​​​​/Xs | Add Xs to the T | - | +| PUT | /Ts/{​​​​id}​​​​/Xs | Replace Xs of the T | - | +| DELETE | /Ts/{​​​​id}​​​​/Xs | Remove Xs from the T | - | + +CRUD endpoints allow the creation and modification of both individual entities and the relations +between objects - starting from the child and the parent. + +Swagger UI for creating offered resources: + +![Swagger API Offers](../../assets/images/swagger_offer.png) + +Swagger UI for adding offers to catalogs: + +![Swagger API Offers to Catalogs](../../assets/images/swagger_offer_catalogs.png) + +Swagger UI for adding offers to catalogs: + +![Swagger API Catalog to Offers](../../assets/images/swagger_catalogs_offer.png) + +As described [here](../features.md), the Dataspace Connector partly supports HATEOAS and +returns correct response codes according to the HTTP1.1 standard (RFC 7231). The OpenApi +documentation is provided within the repository and can additionally be created at runtime as +explained [here](../deployment/build.md#maven). + +The entry point for the REST API is located at `/api`. From there, you can easily navigate through +the data model. + +![REST API](../../assets/images/rest_api.png) + +The API supports pagination and each REST resource provides meta information about +itself. This includes for example the self-link or parent and child information. + +![REST Example Offer](../../assets/images/rest_offer.png) diff --git a/docs/pages/documentation/usage-control.md b/docs/pages/documentation/usage-control.md new file mode 100644 index 000000000..e467b3f76 --- /dev/null +++ b/docs/pages/documentation/usage-control.md @@ -0,0 +1,474 @@ +--- +layout: default +title: Usage Control +nav_order: 5 +description: "" +permalink: /Documentation/UsageControl +parent: Documentation +--- + +# IDS Usage Control Policies +{: .fs-9 } + +Usage policies are an important aspect of IDS, further details are explained on this page. +{: .fs-6 .fw-300 } + +--- + +The Dataspace Connector supports usage policies written +in the `IDS Usage Control Language` based on [ODRL](https://www.w3.org/TR/odrl-model/#policy). + +"An IDS Contract is implicitly divided to two main sections: the contract specific metadata and the +`IDS Usage Control Policy` of the contract. +The contract specific information (e.g., date when the contract has been issued or references to the +sensitive information about the involved parties) has no effect on the enforcement. However, the +`IDS Usage Control Policy` is the key motive of organizational and technical Usage Control +enforcement. +Furthermore, an `IDS Usage Control Policy` contains several Data Usage Control statements (e.g., +permissions, prohibitions and obligations) called `IDS Rules` and is specified in the `IDS Usage +Control Language` which is a technology independent language. The technically enforceable rules +shall be transformed to a technology dependent policy (e.g., MYDATA) to facilitate the Usage Control +enforcement of data sovereignty." (p.22, [IDSA Position Paper Usage Control in the IDS](https://internationaldataspaces.org/wp-content/uploads/IDSA-Position-Paper-Usage-Control-in-the-IDS-V3.0.pdf)) + +## Policy Patterns + +Following the specifications of the IDSA Position Paper about Usage Control, the IDS defines 21 +policy classes. The Dataspace Connector currently implements eight of these. + +Examples for each of them can be found by using the endpoint `POST /api/examples/policy`. +The usage policy is added to the metadata of a resource. The classes at +`io.dataspaceconnector.services.usagecontrol` read, classify, verify, and enforce the policies at +runtime. + +| No. | Title | Support | Implementation | +|:----|:-----------------------------------------------|:-------:|:-------| +| 1 | Allow the Usage of the Data | x | provides data usage without any restrictions +| 2 | Connector-restricted Data Usage | x | allows data usage for a specific connector +| 3 | Application-restricted Data Usage | - | +| 4 | Interval-restricted Data Usage | x | provides data usage within a specified time interval +| 5 | Duration-restricted Data Usage | x | allows data usage for a specified time period +| 6 | Location Restricted Policy | - | +| 7 | Perpetual Data Sale (Payment once) | - | +| 8 | Data Rental (Payment frequently) | - | +| 9 | Role-restricted Data Usage | - | +| 10 | Purpose-restricted Data Usage Policy | - | +| 11 | Event-restricted Usage Policy | - | +| 12 | Restricted Number of Usages | x | allows data usage for n times +| 13 | Security Level Restricted Policy | - | +| 14 | Use Data and Delete it After | x | allows data usage within a specified time interval with the restriction to delete it at a specified time stamp +| 15 | Modify Data (in Transit) | - | +| 16 | Modify Data (in Rest) | - | +| 17 | Local Logging | x | allows data usage if logged to the Clearing House +| 18 | Remote Notifications | x | allows data usage with notification message +| 19 | Attach Policy when Distribute to a Third-party | - | +| 20 | Distribute only if Encrypted | - | +| 21 | State Restricted Policy | - | + +## Pattern Examples + +### Provide Access +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/770f890f-9ea1-4cd6-9f87-8d8d3f126188", + "ids:target": [...], + "ids:description": [ + { + "@value": "provide-access", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Allow Data Usage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Prohibit Access +``` +{ + "@type": "ids:Prohibition", + "@id": "https://w3id.org/idsa/autogen/prohibition/cc051cd8-061a-4169-a031-22ffb7706b7e", + "ids:target": [...], + "ids:description": [ + { + "@value": "prohibit-access", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### N Times Usage +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/bf4de731-4320-4485-aed1-64735aa3e80c", + "ids:target": [...], + "ids:description": [ + { + "@value": "n-times-usage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/3d453647-a3c7-4bd8-840b-8f245991b635", + "ids:rightOperand": { + "@value": "5", + "@type": "xsd:double" + }, + "ids:leftOperand": { + "@id": "idsc:COUNT" + }, + "ids:operator": { + "@id": "idsc:LTEQ" + } + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Duration Usage +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/7f27821b-dd4c-416a-a9f8-0b163709c4d0", + "ids:target": [...], + "ids:description": [ + { + "@value": "duration-usage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/aca6428e-01f5-4012-a598-0e03b02133c8", + "ids:rightOperand": { + "@value": "PT1M30.5S", + "@type": "xsd:duration" + }, + "ids:leftOperand": { + "@id": "idsc:ELAPSED_TIME" + }, + "ids:operator": { + "@id": "idsc:SHORTER_EQ" + } + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Usage During Interval +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/d77fcb4e-9b84-4cd7-967c-fe56ec6d544c", + "ids:target": [...], + "ids:description": [ + { + "@value": "usage-during-interval", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/52556f49-cc86-4074-b87f-9404acae4442", + "ids:rightOperand": { + "@value": "2020-07-11T00:00:00Z", + "@type": "xsd:dateTimeStamp" + }, + "ids:leftOperand": { + "@id": "idsc:POLICY_EVALUATION_TIME" + }, + "ids:operator": { + "@id": "idsc:AFTER" + } + }, + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/8c6937da-cfb2-4a86-be3e-19a2631bfcc0", + "ids:rightOperand": { + "@value": "2020-07-11T00:00:00Z", + "@type": "xsd:dateTimeStamp" + }, + "ids:leftOperand": { + "@id": "idsc:POLICY_EVALUATION_TIME" + }, + "ids:operator": { + "@id": "idsc:BEFORE" + } + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Usage Until Deletion +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/5b4b134f-e821-4fff-9ec3-4819c2af1cea", + "ids:target": [...], + "ids:description": [ + { + "@value": "usage-until-deletion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:postDuty": [ + { + "@type": "ids:Duty", + "@id": "https://w3id.org/idsa/autogen/duty/7cb69671-0b6a-40d3-95f1-f94b3ed00810", + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/a3adedd5-4da7-4d91-8351-93521874f26b", + "ids:rightOperand": { + "@value": "2020-07-11T00:00:00Z", + "@type": "xsd:dateTimeStamp" + }, + "ids:leftOperand": { + "@id": "idsc:POLICY_EVALUATION_TIME" + }, + "ids:operator": { + "@id": "idsc:TEMPORAL_EQUALS" + } + } + ], + "ids:action": [ + { + "@id": "idsc:DELETE" + } + ] + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/197bef7a-09e0-4d10-ad36-a18be9e885f1", + "ids:rightOperand": { + "@value": "2020-07-11T00:00:00Z", + "@type": "xsd:dateTimeStamp" + }, + "ids:leftOperand": { + "@id": "idsc:POLICY_EVALUATION_TIME" + }, + "ids:operator": { + "@id": "idsc:AFTER" + } + }, + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/4c3f8547-2246-440e-9011-41b916767ee7", + "ids:rightOperand": { + "@value": "2020-07-11T00:00:00Z", + "@type": "xsd:dateTimeStamp" + }, + "ids:leftOperand": { + "@id": "idsc:POLICY_EVALUATION_TIME" + }, + "ids:operator": { + "@id": "idsc:BEFORE" + } + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Usage Logging +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/5f1afeed-41f3-4637-870a-2d73cca75fc7", + "ids:target": [...], + "ids:description": [ + { + "@value": "usage-logging", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:postDuty": [ + { + "@type": "ids:Duty", + "@id": "https://w3id.org/idsa/autogen/duty/ae908464-11c0-43e8-80d2-f19145814fdb", + "ids:action": [ + { + "@id": "idsc:LOG" + } + ] + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Usage Notification +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/c23b0a48-693d-41ff-908b-86c4560f7daa", + "ids:target": [...], + "ids:description": [ + { + "@value": "usage-notification", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:postDuty": [ + { + "@type": "ids:Duty", + "@id": "https://w3id.org/idsa/autogen/duty/f0329f3f-ad81-4eea-98d8-833ae9269dfd", + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/b09b4d0d-9e7e-483c-ba3b-07b1614a8fc1", + "ids:rightOperand": { + "@value": "https://localhost:8080/api/ids/data", + "@type": "xsd:anyURI" + }, + "ids:leftOperand": { + "@id": "idsc:ENDPOINT" + }, + "ids:operator": { + "@id": "idsc:DEFINES_AS" + } + } + ], + "ids:action": [ + { + "@id": "idsc:NOTIFY" + } + ] + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` + +### Connector Restricted Usage +``` +{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/0c2b412d-f5ad-47ee-b715-0a8827963844", + "ids:target": [...], + "ids:description": [ + { + "@value": "connector-restriction", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/b3da9618-8f80-4cfe-873b-095efe725901", + "ids:rightOperand": { + "@value": "https://example.com", + "@type": "xsd:anyURI" + }, + "ids:leftOperand": { + "@id": "idsc:SYSTEM" + }, + "ids:operator": { + "@id": "idsc:SAME_AS" + } + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ] +} +``` diff --git a/docs/pages/faq.md b/docs/pages/faq.md new file mode 100644 index 000000000..763d14938 --- /dev/null +++ b/docs/pages/faq.md @@ -0,0 +1,30 @@ +--- +layout: default +title: FAQ +nav_order: 10 +description: "" +permalink: /Faq +--- + +# Frequently Asked Questions +{: .fs-9 } + +You have encountered an apparently unsolvable problem? Maybe someone else has already found a solution. +{: .fs-6 .fw-300 } + +[Issues](https://github.com/International-Data-Spaces-Association/DataspaceConnector/issues){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Discussions](https://github.com/International-Data-Spaces-Association/DataspaceConnector/discussions){: .btn .fs-5 .mb-4 .mb-md-0 } + +--- + +Since the Dataspace Connector is continuously being optimized and extended, it is not possible to +provide a comprehensive list at this location. GitHub comes with the functionalities to create +Issues and Discussions to facilitate a transparent development and exchange. Feel free to check out +the corresponding pages and see if your questions have already been answered. If not, please feel +free to open a new thread, so the community can help you. + +## Still using an old version? + +Since we do not support old versions, our documentation refers only to the latest version of the +Dataspace Connector. Nevertheless, the documentation for v4.x.x, that was previously located in the +GitHub [wiki](https://github.com/International-Data-Spaces-Association/DataspaceConnector/wiki), +can be found [here](assets/files/dsc_v4_wiki.zip). diff --git a/docs/pages/features.md b/docs/pages/features.md new file mode 100644 index 000000000..74c00ddb8 --- /dev/null +++ b/docs/pages/features.md @@ -0,0 +1,81 @@ +--- +layout: default +title: Features +nav_order: 3 +description: "" +permalink: /Features +--- + +# Features +{: .fs-9 } + +Find here an overview of functionalities, used IDS libraries, and integrated IDS components. +{: .fs-6 .fw-300 } + +--- + +The Dataspace Connector uses modern technologies, standards (e.g. RFC 7231, IDS Information Model, +IDS Usage Control Language), and best practices (pattern implementation, e.g. MVC). +Software quality is ensured by adhering to and implementing code style guides and logging and +providing high test coverage. Quality checks and project reports can be generated via maven plugin. + +`Java` `Maven` `Spring Boot` `Spring Data JPA` `Spring Security` `OpenAPI` `HATEOAS` `Swagger` +`LOG4J2` `Docker` `Kubernetes` `JSON(-LD)` `Jaeger` `TLS` + +All functionalities and architectural decisions aim at providing a maintainable and easily +extensible software that encapsulates the IDS information model from connected systems. + +* Identity management: Central Identity Provider/DAPS, IDS certificates (X.509v3) +* API for (meta) data management and IDS communication + * Partially support of HATEOAS + * Management of metadata (optionally also data) in local database (e.g. PostgreSQL) + * Connection of remote data sources (possibility of queries on data sets) +* Clear interfaces between data model and the IDS Infomodel + * Strict implementation of MVC pattern for data management + * Strict access control to backend, information can only be read and changed by services + * Strict state validation for entities via factory classes + * Storage of remote IDs and addresses to objects for origin tracking +* Interaction with other IDS components (as data provider & consumer). + * TLS encrypted communication: IDS Multipart Messages + * Automated messaging sequence + * IDS Metadata Broker (Clearing House, AppStore, ParIS) +* IDS Usage Control Language: eight supported Usage Control Patterns, Policy Negotiation +* Resource Update Messages for receiving latest data +* Integration and configuration of Jaeger for using open telemetry +* Optional http tracing for transparent information and data flow +* Security + * Prevent leaking of technology stack in case of errors/exceptions + * Logger sanitizes inputs to prevent CRLF injections + * Common CVE patches + + +## Libraries + +| Library | License | Owner | Contact | +|:--------|:--------|:------|:--------| +| [IDS Information Model Library](https://maven.iais.fraunhofer.de/artifactory/eis-ids-public/de/fraunhofer/iais/eis/ids/infomodel/) | [Apache 2.0](https://github.com/International-Data-Spaces-Association/Java-Representation-of-IDS-Information-Model) | Fraunhofer IAIS | [E-Mail](mailto:contact@ids.fraunhofer.de) | +| [IDS Information Model Serializer Library](https://maven.iais.fraunhofer.de/artifactory/eis-ids-public/de/fraunhofer/iais/eis/ids/infomodel-serializer/) | Apache 2.0 | Fraunhofer IAIS | [E-Mail](mailto:contact@ids.fraunhofer.de) | +| [IDS Framework](https://mvn.ids.isst.fraunhofer.de/nexus/repository/ids-public/) | [Apache 2.0](https://github.com/International-Data-Spaces-Association/IDS-Connector-Framework) | Fraunhofer ISST | [Tim Berthold](mailto:tim.berthold@isst.fraunhofer.de) | +| IDS Messaging Service | [Apache 2.0](https://github.com/International-Data-Spaces-Association/IDS-Messaging-Services) | Fraunhofer ISST & AISEC | [Tim Berthold](mailto:tim.berthold@isst.fraunhofer.de), [Matthias Böckmann](mailto:matthias.boeckmann@iais.fraunhofer.de) | + +The [ConfigManager](https://github.com/FraunhoferISST/IDS-ConfigurationManager) and its +[GUI](https://github.com/International-Data-Spaces-Association/IDS-ConfigurationManager-UI) are a +part of the IDS Connector and aim to facilitate the configuration of the Dataspace Connector and +further IDS Connector implementations. Both projects are also open source and licensed under +Apache 2.0. + +The table below will document the compatibility of the component's versions. + +| DSC Core Version | ConfigManager | ConfigManager GUI | +|:-----------------|:--------------|:------------------| +| v4.x.x | v6.0.0 | v5.0.0 | +| v5.x.x | - | - | + + +## IDS Communication + +| Component | License | Owner | Contact | +|:--------|:--------|:------|:--------| +| [IDS Broker](https://broker.ids.isst.fraunhofer.de/) | [open core](https://github.com/International-Data-Spaces-Association/metadata-broker-open-core) | Fraunhofer IAIS | [E-Mail](mailto:contact@ids.fraunhofer.de) | +| [DAPS](https://daps.aisec.fraunhofer.de/) | [Apache 2.0](https://github.com/Fraunhofer-AISEC/omejdn-server) | Fraunhofer AISEC | [Gerd Brost](mailto:gerd.brost@aisec.fraunhofer.de) | +| [ParIS](https://paris.ids.isst.fraunhofer.de/) | [open core](https://github.com/International-Data-Spaces-Association/ParIS-open-core) | Fraunhofer IAIS | [E-Mail](mailto:contact@ids.fraunhofer.de) diff --git a/docs/pages/getting-started.md b/docs/pages/getting-started.md new file mode 100644 index 000000000..d36cc57fc --- /dev/null +++ b/docs/pages/getting-started.md @@ -0,0 +1,104 @@ +--- +layout: default +title: Getting Started +nav_order: 4 +description: "" +permalink: /GettingStarted +--- + +# Getting Started +{: .fs-9 } + +Get an example setup running without diving into the code. +{: .fs-6 .fw-300 } + +--- + +## Quick Start + +We provide Docker images. These can be found [here](https://github.com/orgs/International-Data-Spaces-Association/packages/container/package/dataspace-connector). +The GitHub container registry (GHCR) allows to download Docker images without credentials. +You will find an image for each release tag with the corresponding version. In addition, an image of +the master and dev branch is automatically provided as soon as changes are identified. + +If you want to build and run locally, ensure that at least Java 11 is installed. Then, follow these +steps: + +1. Clone the repository: `git clone https://github.com/International-Data-Spaces-Association/DataspaceConnector.git`. +2. Execute `cd DataspaceConnector` and `./mvnw clean package`. +3. Navigate to `/target` and run `java -jar dataspaceconnector-{VERSION}.jar`. +4. If everything worked fine, the connector is available at + [https://localhost:8080/](https://localhost:8080/). The API can be accessed at + [https://localhost:8080/api](https://localhost:8080/api). The Swagger UI can be found at + [https://localhost:8080/api/docs](https://localhost:8080/api/docs). + +For a more detailed explanation, see [here](deployment.md). + + +## Test Deployments + +The IDSA community provides a free +[GitHub repository](https://github.com/International-Data-Spaces-Association/IDS-Deployment-Examples) +with sample deployments. These include not only the Dataspace Connector or a deployment together +with ConfigManager and GUI, but also some with other IDS components. The goal is to provide an easy +entry into the whole IDS ecosystem. Feel free to have a look at the files or contribute with your +own examples. + + +## Test Instances + +An instance of the Dataspace Connector is currently available in the IDS Lab at +[https://simpleconnector.ids.isst.fraunhofer.de/](https://simpleconnector.ids.isst.fraunhofer.de/). +It can only be reached from inside a VPN network. To get your IP address unblocked, please contact +[us](mailto:info@dataspace-connector.de). +* The connector self-description is available at [https://simpleconnector.ids.isst.fraunhofer.de/](https://simpleconnector.ids.isst.fraunhofer.de/) (GET). +* The **open endpoint for IDS communication** is + [https://simpleconnector.ids.isst.fraunhofer.de/api/ids/data](https://simpleconnector.ids.isst.fraunhofer.de/api/ids/data) (POST). +* The backend API and its endpoints (`/api/**`) are only accessible to users with admin rights. + +### Connector Communication +When requesting the connector's self-description, the included catalog gives information about +available resources. The resource id is essential for requesting an artifact or description. + +The open endpoint at `/api/ids/data` expects an `ArtifactRequestMessage` with a known artifact id +as `RequestedArtifact` (for requesting data) or a `DescriptionRequestMessage` with a known +element id as `RequestedElement` (for requesting metadata). +* If this parameter is not known to the connector, you will receive a `RejectionMessage` as + response. +* If the `RequestedElement` is missing at a `DescriptionRequestMessage`, you will receive the + connector's self-description. + +Possible rejection messages: +* `RejectionMessage` with `RejectionReason.VERSION_NOT_SUPPORTED` if you are not using + Infomodel v4.x.x. +* `RejectionMessage` with `RejectionReason.NOT_AUTHENTICATED` if the requesting connector has no + valid DAT. +* `RejectionMessage` with `RejectionReason.BAD_PARAMETERS` if the request contains missing/wrong + parameters. +* `RejectionMessage` with `RejectionReason.INTERNAL_RECIPIENT_ERROR` if message processing failed. +* `RejectionMessage` with `RejectionReason.NOT_FOUND` if the requested element/artifact could not + be found. +* `RejectionMessage` with `RejectionReason.NOT_AUTHORIZED` if a policy restriction was detected. +* `ContractRejectionMessage` with `RejectionReason.BAD_PARAMETERS` if the contract request was not + accepted. + +### More IDS + +Other IDS components also have running instances that can be used for testing. The Dataspace +Connector currently mainly supports communication with the IDS Broker and DAPS - as described +[here](features.md#ids-communication). A working communication with other components is not +guaranteed. + +* The Dynamic Attribute Provisioning Service (DAPS) is available at +[https://daps.aisec.fraunhofer.de/](https://daps.aisec.fraunhofer.de/). + +* The IDS Metadata Broker is available at +[https://broker.ids.isst.fraunhofer.de](https://broker.ids.isst.fraunhofer.de). It expects IDS +multipart messages at[https://broker.ids.isst.fraunhofer.de/infrastructure](https://broker.ids.isst.fraunhofer.de/infrastructure). +The GUI can be accessed at [https://broker.ids.isst.fraunhofer.de/browse](https://broker.ids.isst.fraunhofer.de/browse). + +* The Participant Information System (ParIS) is available at +[https://paris.ids.isst.fraunhofer.de](https://paris.ids.isst.fraunhofer.de). +It expects IDS multipart messages at +[https://paris.ids.isst.fraunhofer.de/infrastructure](https://paris.ids.isst.fraunhofer.de/infrastructure). +The GUI can be accessed at [https://paris.ids.isst.fraunhofer.de/browse](https://paris.ids.isst.fraunhofer.de/browse). diff --git a/docs/pages/introduction.md b/docs/pages/introduction.md new file mode 100644 index 000000000..674fcd43e --- /dev/null +++ b/docs/pages/introduction.md @@ -0,0 +1,66 @@ +--- +layout: default +title: Introduction +nav_order: 2 +description: "" +permalink: /Introduction +--- + +# Introduction +{: .fs-9 } + +New to IDS? Get a short introduction and some information about the IDS Connector at a glance. +{: .fs-6 .fw-300 } + +--- + +The IDS Connector is the core of the data space. Is the gateway to connect existing systems and +their data to an IDS ecosystem. Its architecture and functionalities are defined by the IDS RAM and +specified by the certification criteria. + +The IDS Connector allows to exchange data and enrich it with metadata. An important aspect of this +are usage conditions, which can be defined, administrated, and implemented by the Connector. The +metadata is described by the ontology of the IDS Information Model. The main advantage of the IDS +Reference architecture and the use of an IDS Connector is the decentralized data storage. This +enables data integration from different data sources and allows data access exclusively through +other IDS Connectors. Thus, a technical implementation of data sovereignty is guaranteed. + +![RAM Connector Architecture](../assets/images/ram_architecture.png) + +An IDS Connector is composed of various system services: +* Execution core container with Message Systems (Message Router/Bus) +* Configuration Manager to configure the Connector (Execution Core Container, Application Container + Management, Network, Firewalls, etc.) +* Data Apps for data processing and handling +* Application Container Management +* Hardware/Operating System + +Primarily to establish trust, the IDS Connector also addresses Application Container Management, +Network, OS, Hardware, etc. Furthermore, trust between different IDS participants is ensured by +certifying the software components used, i.a. the IDS Connector. Thereby, three different trust +levels are defined: base, trust, and trust+. All profiles comprise IDS-specific requirements and +some from Secure Development and the DIN standard for IT security for industrial automation systems +(DIN EN IEC 62443-4-2). Details are listed [here](roadmap/concept.md). + +## Reference Implementation + +The Dataspace Connector is one of several implementation of an IDS Connector. It is the reference +implementation of the data economy departement of the Fraunhofer Institute for Software and Systems +Engineering (ISST). It is maintained as an open source software project on GitHub, supported and +promoted by IDSA. + +In addition to the project, other components are also being developed open source. For example, the +ConfigManager can be used to configure the connector and enable user-friendly interactions via a GUI. +Find more details [here](features.md#libraries). + +The Dataspace Connector is designed to provide an easy entry point into the IDS Ecosystem, enabling +projects and companies to connect to Data Spaces and exchange data in a sovereign way. Thereby, the +defined usage policies are not only transferred between the IDS participants, but also directly +enforced. How the architecture is designed can be seen [here](documentation/architecture.md). + + +## Links + +[IDS Information Model](https://international-data-spaces-association.github.io/InformationModel/docs/index.html) • +[IDS RAM](https://internationaldataspaces.org/use/reference-architecture/) • +[IDSA Jive](https://internationaldataspaces.org/make/communities/) diff --git a/docs/pages/roadmap.md b/docs/pages/roadmap.md new file mode 100644 index 000000000..490687fb1 --- /dev/null +++ b/docs/pages/roadmap.md @@ -0,0 +1,21 @@ +--- +layout: default +title: Roadmap +nav_order: 9 +description: "" +permalink: /Roadmap +has_children: true +has_toc: true + +--- + +# Roadmap +{: .fs-9 } + +Ensuring developments following the same goal, the project's roadmap and each aspect are described in detail. +{: .fs-6 .fw-300 } + +--- + +In this section you will find planned features and the ones that are specified by the IDS +certification criteria. diff --git a/docs/pages/roadmap/concept.md b/docs/pages/roadmap/concept.md new file mode 100644 index 000000000..d562a6d1d --- /dev/null +++ b/docs/pages/roadmap/concept.md @@ -0,0 +1,127 @@ +--- +layout: default +title: IDS-ready +nav_order: 2 +description: "" +permalink: /Roadmap/Ids-ready +parent: Roadmap +--- + +# IDS-ready Implementation Concept +{: .fs-9 } + +Following the IDS certification criteria, an IDS Connector has to provide certain software criteria. +{: .fs-6 .fw-300 } + +--- + +The Dataspace Connector is "IDS-ready" approved since 03.12.2020, for the certification level base +(+ usage control). The ids-ready implementation concept can be found +[here](assets/files/DSC_implementation_concept_ids_ready_v4.pdf). In the table below is an overview +of all criteria for the base profile and whether it is already implemented or not. A more detailed +description of the individual issues can be found in the PDF file. + +| DSC | No. | Title | +|:---:|:------------|:------------| +| x | COM 01 | Protected connection | +| x | COM 02 | Mutual authentication | +| x | COM 03 | State of the art cryptography | +| x | USC 01 | Definition of usage policies | +| x | _USC 02_ | _Sending of usage policies_ | +| x | _USC 03_ | _Usage policy enforcement_ | +| x | INF 01 | Self-Description (at Connector) | +| x | INF 02 | Self-Description (at Broker) | +| x | INF 03 | Self-Description content | +| x | INF 04 | Self-Description evaluation | +| x | INF 05 | Dynamic attribute tokens | +| x | IAM 01 | Connector identifier | +| x | IAM 02 | Time Service | +| x | IAM 03 | Online certificate status check | +| x | IAM 04 | Attestation of dynamic attributes | +| - | BRK 01 | Broker service inquiries | +| x | BRK 02 | Broker registration | +| x | BRK 03 | Broker registration update | +| - | OS 01 | Container support | +| - | APS 01 | App signature | +| - | APS 02 | App signature verification | +| - | APS 05 | App installation | +| - | APS 06 | App Store | +| - | AUD 01 | Access control logging | +| - | AUD 02 | Data access logging | +| - | AUD 03 | Configuration changes logging | +| x | CR 1.1 | Human user identification and authentication | +| - | CR 1.1 (1) | Unique identification and authentication | +| x | CR 1.2 | Software process and device identification and authentication | +| x | CR 1.2 (1) | Unique identification and authentication | +| - | CR 1.3 | Account management | +| - | CR 1.4 | Identifier management | +| - | CR 1.5 | Authenticator management | +| - | CR 1.7 | Strength of password-based authentication | +| - | CR 1.8 | Public key infrastructure certificates | +| - | CR 1.9 | Strength of public key-based authentication | +| x | CR 1.10 | Authenticator feedback | +| - | CR 1.11 | Unsuccessful login attempts | +| - | CR 1.12 | System use notification | +| - | CR 1.14 | Strength of symmetric key-based authentication | +| x | CR 2.1 | Authorization enforcement | +| - | CR 2.2 | Wireless use control | +| - | CR 2.5 | Session lock | +| - | CR 2.8 | Auditable events | +| - | CR 2.9 | Audit storage capacity | +| - | CR 2.10 | Response to audit processing failures | +| x | CR 2.11 | Timestamps | +| - | CR 2.12 | Non-repudiation | +| x | CR 3.1 | Communication integrity | +| x | CR 3.1 (1) | Communication authentication | +| - | CR 3.3 | Security functionality verification | +| - | CR 3.4 | Software and information integrity | +| - | CR 3.5 | Input validation | +| - | CR 3.6 | Deterministic output | +| x | CR 3.7 | Error handling | +| - | CR 3.8 | Session integrity | +| x | CR 4.1 | Information confidentiality | +| - | CR 4.2 (1) | Erase of shared memory resources | +| x | CR 4.3 | Use of cryptography | +| - | CR 5.1 | Network segmentation | +| - | CR 6.1 | Audit log accessibility | +| - | CR 7.1 | Denial of service protection | +| - | CR 7.2 | Resource management | +| - | CR 7.3 | Control system backup | +| - | CR 7.4 | Control system recovery and reconstitution | +| - | CR 7.6 | Network and security configuration settings | +| x | CR 7.7 | Least functionality | +| - | SAR 2.4 | Mobile code | +| - | SAR 2.4 (1) | Mobile code integrity check | +| - | SAR 2.4 (1) | Protection from malicious code | +| - | NDR 1.6 | Wireless Access Management | +| - | NDR 1.13 | Access via untrusted networks | +| - | NDR 2.4 | Mobile code | +| - | NDR 3.2 | Protection from malicious code | +| - | NDR 3.10 | Support for updates | +| - | NDR 3.14 | Integrity of the boot process | +| - | NDR 5.2 | Zone boundary protection | +| - | NDR 5.3 | General purpose, person-to-person communication restrictions | +| - | D_AD.1 | Secure initialisation | +| - | D_AD.2 | Tamper protection | +| - | D_AD.3 | Security-enforcing mechanisms | +| x | D_IS.1 | Interface purpose and usage | +| x | D_IS.2 | Interface parameters | +| x | D_DD.1 | Subsystem structure | +| - | G_AP.1 | Acceptance procedures | +| x | G_AP.2 | Installation procedures | +| x | G_OG.1 | Interface usage for each user role | +| - | G_OG.2 | Possible modes of operation | +| x | S_CM.1 | Unique component reference | +| x | S_CM.2 | Consistent usage of component reference | +| - | S_CM.6 (1) | Configuration list content (1) | +| - | S_CM.7 | Unique identification based on configuration list | +| - | S_CM.8 | Developer Information | +| - | S_DL.1 | Secure delivery | +| - | S_FR.1 | Tracking of reported security flaws | +| - | S_FR.2 | Security flaw description | +| - | S_FR.3 | Status of corrective measures | +| x | T_CA.1 | Test coverage analysis | +| - | T_CA.2 | Test procedures for subsystems | +| - | T_TD.1 | Test documentation | +| - | T_TD.2 | Test configuration | +| - | T_TD.3 | Ordering Dependencies | diff --git a/docs/pages/roadmap/planned.md b/docs/pages/roadmap/planned.md new file mode 100644 index 000000000..474908002 --- /dev/null +++ b/docs/pages/roadmap/planned.md @@ -0,0 +1,99 @@ +--- +layout: default +title: Planned Features +nav_order: 1 +description: "" +permalink: /Features/PlannedFeatures +parent: Roadmap +--- + +# Planned Features +{: .fs-9 } + +For transparent and collaborative development, the project's roadmap and each aspect are described in detail. +{: .fs-6 .fw-300 } + +--- + +Some requirements are derived from the IDS-ready implementation concept as listed +[here](concept.md). Below, you can find an overview of general requirements that will be +addressed in future development. We distinguish between core and ids functionality. + +## Core-Functionality + +This list follows no timeline. Instead, individual tasks can be priority-assigned. Thus, the +implementation can be freely scheduled and extended, depending on the interests and projects of +potential contributors. The rating ranges from 1 (= high priority) to 3 (= low priority). + +| Priority | Task | Status | Note | +|:---------|:--------------------------------------|:------------|:------------| +| 2 | Connector Orchestration in Kubernetes | in progress | | +| 2 | Network Support | | | +| 2 | Identity & Access Management | | | +| 2 | Software Tests | ongoing | | +| 1 | Software Documentation | ongoing | | +| 3 | Connection to Hyperscalers as e.g. AWS| | | +| 1 | Audit Logging | | | +| 1 | Code Quality Improvements | done | | +| 1 | Database Improvement | done | | +| 1 | Exception Handling | done | | +| 1 | Logging | done | | +| 3 | Scalability | | | +| 2 | Data Streaming | | | +| 2 | Asynchronous Message Handling | | | + +## IDS-Functionality + +The implementation of the following requirements primarily focuses on IDS developments. +Responsible for this are mainly developers from the Fraunhofer ISST. + +| Timeline | Task | Status | Note | +|:---------|:---------------------------------------|:------------|:------------| +| Q4/20 | Ids-ready Test | done | | +| | ConfigManager Integration | done | | +| | Basic Policy Negotiation | done | | +| Q1/21 | Routing (e.g. Apache Camel) | in progress | integration via ConfigManager | +| | App (Store) Integration | in progress | integration via ConfigManager | +| | Query Broker | in progress | integration via ConfigManager | +| | Integration of IDSCP 2.0 | - | postponed to Q2 | +| | IDS-LDP Integration | - | postponed to Q2 | +| | Support Query Parameters | done | | +| | Basic Clearing House Integration | in progress | postponed to Q2 | +| Q2/21 | Usage Control Extension | | | +| | Support ParIS | | | +| | Support Vocabulary Provider | | | +| | Extended Clearing House Integration | | | +| | Support of complex REST backends | | | +| | Data Push | | core functionality that must follow the IDS concepts | +| | Publish/Subscribe | | core functionality that must follow the IDS concepts | + +## Details + +Please find some detailed explanation of the aforementioned roadmap items below. + +* Ids-ready Test: The test for the IDS-ready label comprises different requirements, which are + assigned to the 3 levels base, trust and trust+. The last plugfest has shown that these 3 possible + levels will be extended. With the DSC, we are aiming for the base profile. Currently, we are + writing a concept for the IDS-ready label test. This includes exactly the criteria that will also + be checked during the later certification. At that time, however, the connector has to actually + implement the requirements. +* ConfigManager Integration: The Configuration Manager (CM) provides a user-friendly + interaction with the Dataspace Connector's endpoints by using a GUI. For compatibility, some + further endpoints may need to be implemented and the structure of individual functionalities has + to be modularized. +* Routing (e.g. Apache Camel): As described [here](../documentation/architecture.md), the + integration of a routing framework as e.g. Apache Camel will improve and extend the data flow and + its interception. +* Integration of IDSCPv2: To support higher security profiles, the IDSCP 2.0 communication library + is integrated into the IDS Framework used by the DSC. Possibly some modifications of the DSC will + be necessary. +* Usage Control Extension: Currently, the Dataspace Connector supports eight policy patterns out + of 21. Policy enforcement should be configurable. For this, the current access and usage control + must be modular and interchangeable. As a possible alternative, MyData and a corresponding + interceptor for the data flow will be integrated into the DSC. On top of that, the Connector + should provide a possibility to define custom policy patterns. +* Support ParIS: In order to provide additional information about the IDS participants, the DSC + needs the connection of the Participant Information Service (ParIS) provided by the Fraunhofer + IAIS. +* Support Vocabulary Provider: For the integration of user-specific vocabularies for data + description, the Vocabulary Provider VoCol will be integrated. diff --git a/images/.gitkeep b/images/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/images/artifact.PNG b/images/artifact.PNG deleted file mode 100644 index c758921c1..000000000 Binary files a/images/artifact.PNG and /dev/null differ diff --git a/images/backend.PNG b/images/backend.PNG deleted file mode 100644 index b72e896fa..000000000 Binary files a/images/backend.PNG and /dev/null differ diff --git a/images/brokercommunication.PNG b/images/brokercommunication.PNG deleted file mode 100644 index 14e8467cf..000000000 Binary files a/images/brokercommunication.PNG and /dev/null differ diff --git a/images/connector.PNG b/images/connector.PNG deleted file mode 100644 index 353935e62..000000000 Binary files a/images/connector.PNG and /dev/null differ diff --git a/images/connector_architecture.png b/images/connector_architecture.png deleted file mode 100644 index a27f31bf3..000000000 Binary files a/images/connector_architecture.png and /dev/null differ diff --git a/images/data-handshake.png b/images/data-handshake.png deleted file mode 100644 index 53dde8dd4..000000000 Binary files a/images/data-handshake.png and /dev/null differ diff --git a/images/description.PNG b/images/description.PNG deleted file mode 100644 index dbb000972..000000000 Binary files a/images/description.PNG and /dev/null differ diff --git a/images/example.PNG b/images/example.PNG deleted file mode 100644 index d844976bd..000000000 Binary files a/images/example.PNG and /dev/null differ diff --git a/images/ids-ready.png b/images/ids-ready.png deleted file mode 100644 index 9f09328bd..000000000 Binary files a/images/ids-ready.png and /dev/null differ diff --git a/images/metadata.PNG b/images/metadata.PNG deleted file mode 100644 index 31f1b0527..000000000 Binary files a/images/metadata.PNG and /dev/null differ diff --git a/images/network_architecture.png b/images/network_architecture.png deleted file mode 100644 index 8e40990cd..000000000 Binary files a/images/network_architecture.png and /dev/null differ diff --git a/images/overall-architecture.png b/images/overall-architecture.png deleted file mode 100644 index 9ed94b0bc..000000000 Binary files a/images/overall-architecture.png and /dev/null differ diff --git a/images/policy-negotiation-sequence.png b/images/policy-negotiation-sequence.png deleted file mode 100644 index 79c793685..000000000 Binary files a/images/policy-negotiation-sequence.png and /dev/null differ diff --git a/images/selfservice.PNG b/images/selfservice.PNG deleted file mode 100644 index 6f0873add..000000000 Binary files a/images/selfservice.PNG and /dev/null differ diff --git a/openapi.yaml b/openapi.yaml index 012175d80..1d176cd12 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,358 +1,372 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + openapi: 3.0.1 info: - title: Dataspace Connector API - description: This is the Dataspace Connector's backend API using springdoc-openapi - and OpenAPI 3. + title: Dataspace Connector + description: IDS Connector originally developed by the Fraunhofer ISST contact: - name: Julia Pampus - email: julia.pampus@isst.fraunhofer.de + name: Fraunhofer Institute for Software and Systems Engineering + url: https://www.dataspace-connector.io/ + email: info@dataspace-connector.de license: - name: Apache 2.0 + name: "Apache License, Version 2.0" url: https://www.apache.org/licenses/LICENSE-2.0.txt - version: v3.2.1 + version: 5.0.0 servers: - - url: https://localhost:8080 + - url: http://localhost:8080 description: Generated server url tags: - - name: 'Backend: Resource Data Handling' - description: Endpoints for resource data handling - - name: 'Connector: Resource Handling' - description: Endpoints for resource handling - - name: 'Connector: IDS Connector Communication' - description: Endpoints for invoking external connector requests - - name: 'Connector: Selfservice' - description: Endpoints for connector information - - name: 'Connector: IDS Broker Communication' - description: Endpoints for invoking broker communication + - name: Resources + description: Endpoints for linking contracts to resources + - name: Artifacts + description: Endpoints for CRUD operations on artifacts + - name: Contracts + description: Endpoints for CRUD operations on contracts + - name: Rules + description: Endpoints for linking rules to contracts + - name: Contracts + description: Endpoints for linking rules to contracts + - name: Agreements + description: Endpoints for linking agreements to artifacts + - name: Resources + description: Endpoints for CRUD operations on offered resources + - name: Resources + description: Endpoints for CRUD operations on requested resources + - name: Artifacts + description: Endpoints for linking artifacts to representations + - name: Contracts + description: Endpoints for linking contracts to requests + - name: Agreements + description: Endpoints for contract/policy handling + - name: Representations + description: Endpoints for linking representations to offered resources + - name: Rules + description: Endpoints for CRUD operations on rules + - name: Representations + description: Endpoints for linking representations to requested resources + - name: Resources + description: Endpoints for linking representations to resources + - name: Artifacts + description: Endpoints for linking artifacts to agreements + - name: Representations + description: Endpoints for linking artifacts to representations + - name: Catalogs + description: Endpoints for linking offered resources to catalogs + - name: Representations + description: Endpoints for CRUD operations on representations + - name: Catalogs + description: Endpoints for CRUD operations on catalogs + - name: Resources + description: Endpoints for linking requested resources to catalogs + - name: Usage Control + description: Endpoints for contract/policy handling + - name: Connector + description: Endpoints for connector information and configuration + - name: IDS Messages + description: Endpoints for invoke sending IDS messages + - name: Contracts + description: Endpoints for linking contracts to offers + - name: Resources + description: Endpoints for linking offered resources to catalogs paths: - /admin/api/broker/resource/{resource-id}/update: - post: + /api/rules/{id}: + get: tags: - - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. - operationId: updateResourceAtBroker + - Rules + summary: Get a base resource by id + operationId: get parameters: - - name: broker - in: query - description: The url of the broker. - required: true - schema: - type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure - - name: resource-id + - name: id in: path - description: The resource id. required: true schema: type: string format: uuid responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/update/{resource-id}/remove: - post: + $ref: '#/components/schemas/ContractRuleView' + put: tags: - - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. - operationId: deleteResourceAtBroker + - Rules + summary: Update a base resource by id + operationId: update parameters: - - name: broker - in: query - description: The url of the broker. - required: true - schema: - type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure - - name: resource-id + - name: id in: path - description: The resource id. required: true schema: type: string format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContractRuleDesc' + required: true responses: - "200": - description: OK + "204": + description: No Content content: '*/*': schema: type: object - /admin/api/broker/register: - post: - tags: - - 'Connector: IDS Broker Communication' - summary: Register Connector - description: Register or update connector at an IDS broker. - operationId: updateAtBroker - parameters: - - name: broker - in: query - description: The url of the broker. - required: true - schema: - type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure - responses: - "200": - description: OK + "201": + description: Created content: '*/*': schema: type: object - /admin/api/broker/update: - post: + delete: tags: - - 'Connector: IDS Broker Communication' - summary: Register Connector - description: Register or update connector at an IDS broker. - operationId: updateAtBroker_1 + - Rules + summary: Delete a base resource by id + operationId: delete parameters: - - name: broker - in: query - description: The url of the broker. + - name: id + in: path required: true schema: type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure + format: uuid responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/broker/unregister: - post: + "204": + description: No Content + /api/rules/{id}/contracts: + get: tags: - - 'Connector: IDS Broker Communication' - summary: Unregister Connector - description: Unregister connector at an IDS broker. - operationId: unregisterAtBroker + - Rules + summary: Get all children of a base resource with pagination + operationId: getResource parameters: - - name: broker - in: query - description: The url of the broker. + - name: id + in: path required: true schema: type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/query: - post: + $ref: '#/components/schemas/PagedModelContractView' + put: tags: - - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. - operationId: queryBroker + - Rules + summary: Replace the children of a base resource + operationId: replaceResources parameters: - - name: broker - in: query - description: The url of the broker. + - name: id + in: path required: true schema: type: string - example: https://broker.ids.isst.fraunhofer.de/infrastructure - responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/example/configuration: - get: - tags: - - 'Connector: Selfservice' - summary: Get Connector configuration - description: Get the connector's configuration. - operationId: getConnector + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/example/usage-policy: + "204": + description: No content post: tags: - - 'Connector: Selfservice' - summary: Get example policy - description: Get an example policy for a given policy pattern. - operationId: getExampleUsagePolicy + - Rules + summary: Add a list of children to a base resource + operationId: addResources parameters: - - name: pattern - in: query - description: The policy pattern. + - name: id + in: path required: true schema: type: string - enum: - - PROVIDE_ACCESS - - PROHIBIT_ACCESS - - N_TIMES_USAGE - - DURATION_USAGE - - USAGE_DURING_INTERVAL - - USAGE_UNTIL_DELETION - - USAGE_LOGGING - - USAGE_NOTIFICATION + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/example/policy-pattern: - post: + $ref: '#/components/schemas/PagedModelContractView' + delete: tags: - - 'Connector: Selfservice' - summary: Get pattern of policy - description: Get the policy pattern represented by a given JSON string. - operationId: getPolicyPattern + - Rules + summary: Remove a list of children from a base resource + operationId: removeResources parameters: - - name: policy - in: query - description: The JSON string representing a policy + - name: id + in: path required: true schema: type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/selfservice: + "204": + description: No content + /api/requests/{id}: get: tags: - - 'Connector: Selfservice' - summary: Connector Self-description - description: Get the connector's self-description. - operationId: getSelfService + - Resources + summary: Get a base resource by id + operationId: get_1 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/request/artifact: - post: + $ref: '#/components/schemas/RequestedResourceView' + put: tags: - - 'Connector: IDS Connector Communication' - summary: Artifact Request - description: 'Request data from another IDS connector. INFO: Before an artifact - can be requested, the metadata must be queried. The key generated in this - process must be passed in the artifact query.' - operationId: requestData + - Resources + summary: Update a base resource by id + operationId: update_1 parameters: - - name: recipient - in: query - description: The URI of the requested IDS connector. - required: true - schema: - type: string - format: uri - example: https://localhost:8080/api/ids/data - - name: requestedArtifact - in: query - description: The URI of the requested artifact. - required: true - schema: - type: string - format: uri - example: https://w3id.org/idsa/autogen/artifact/a4212311-86e4-40b3-ace3-ef29cd687cf9 - - name: key - in: query - description: A unique validation key. + - name: id + in: path required: true schema: type: string format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RequestedResourceDesc' + required: true responses: - "200": - description: OK + "204": + description: No Content content: '*/*': schema: type: object - /admin/api/request/description: - post: + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: tags: - - 'Connector: IDS Connector Communication' - summary: Description Request - description: Request metadata from another IDS connector. - operationId: requestMetadata + - Resources + summary: Delete a base resource by id + operationId: delete_1 parameters: - - name: recipient - in: query - description: The URI of the requested IDS connector. + - name: id + in: path required: true schema: type: string - format: uri - example: https://localhost:8080/api/ids/data - - name: requestedArtifact - in: query - description: The URI of the requested resource. - required: false - schema: - type: string - format: uri - example: https://w3id.org/idsa/autogen/resource/a4212311-86e4-40b3-ace3-ef29cd687cf9 + format: uuid responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/resources/{resource-id}: + "204": + description: No Content + /api/requests/{id}/representations: get: tags: - - 'Connector: Resource Handling' - summary: Get Resource - description: Get the resource's metadata by its uuid. - operationId: getResource + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_1 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + $ref: '#/components/schemas/PagedModelRepresentationView' put: tags: - - 'Connector: Resource Handling' - summary: Update Resource - description: Update the resource's metadata by its uuid. - operationId: updateResource + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_1 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string @@ -361,104 +375,132 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceMetadata' + type: array + items: + type: string + format: uri required: true responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - delete: + "204": + description: No content + post: tags: - - 'Connector: Resource Handling' - summary: Delete Resource - description: Delete a resource by its uuid. - operationId: deleteResource + - Resources + summary: Add a list of children to a base resource + operationId: addResources_1 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/{resource-id}/access: - get: + $ref: '#/components/schemas/PagedModelRepresentationView' + delete: tags: - - 'Connector: Resource Handling' - summary: Get Data Access - description: Get the number of the resource's data access. - operationId: getAccess + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_1 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: - "200": - description: OK - content: - '*/*': - schema: - type: object - /admin/api/resources/{resource-id}/{representation-id}: + "204": + description: No content + /api/requests/{id}/contracts: get: tags: - - 'Connector: Resource Handling' - summary: Get Resource Representation - description: Get the resource's representation by its uuid. - operationId: getRepresentation + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - - name: representation-id - in: path - description: The representation uuid. - required: true + - name: page + in: query + required: false schema: - type: string - format: uuid + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + $ref: '#/components/schemas/PagedModelContractView' put: tags: - - 'Connector: Resource Handling' - summary: Update representation - description: Update a resource's representation by its uuid. - operationId: updateRepresentation + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - - name: representation-id + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Resources + summary: Add a list of children to a base resource + operationId: addResources_2 + parameters: + - name: id in: path - description: The representation uuid. required: true schema: type: string @@ -467,54 +509,109 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceRepresentation' + type: array + items: + type: string + format: uri required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + $ref: '#/components/schemas/PagedModelContractView' delete: tags: - - 'Connector: Resource Handling' - summary: Remove Resource Representation - description: Remove a resource's representation by its uuid. - operationId: deleteRepresentation + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - - name: representation-id - in: path - description: The representation uuid. - required: true - schema: + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/requests/{id}/catalogs: + get: + tags: + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_3 + parameters: + - name: id + in: path + required: true + schema: type: string format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/resource: + $ref: '#/components/schemas/PagedModelCatalogView' + put: + tags: + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_3 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content post: tags: - - 'Connector: Resource Handling' - summary: Register Resource - description: Register a resource by its metadata. - operationId: createResource + - Resources + summary: Add a list of children to a base resource + operationId: addResources_3 parameters: - name: id - in: query - required: false + in: path + required: true schema: type: string format: uuid @@ -522,47 +619,70 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceMetadata' + type: array + items: + type: string + format: uri required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/{resource-id}/contract: + $ref: '#/components/schemas/PagedModelCatalogView' + delete: + tags: + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_3 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/representations/{id}: get: tags: - - 'Connector: Resource Handling' - summary: Get Resource Contract - description: Get the resource's usage policy. - operationId: getContract + - Representations + summary: Get a base resource by id + operationId: get_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + $ref: '#/components/schemas/RepresentationView' put: tags: - - 'Connector: Resource Handling' - summary: Update Resource Contract - description: Update the resource's usage policy. - operationId: updateContract + - Representations + summary: Update a base resource by id + operationId: update_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string @@ -571,34 +691,103 @@ paths: content: application/json: schema: - type: string - description: A new resource contract. + $ref: '#/components/schemas/RepresentationDesc' required: true responses: - "200": - description: OK + "204": + description: No Content content: '*/*': schema: type: object - /admin/api/resources/{resource-id}/representation: - post: + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: tags: - - 'Connector: Resource Handling' - summary: Add Representation - description: Add a representation to a resource. - operationId: addRepresentation + - Representations + summary: Delete a base resource by id + operationId: delete_2 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid + responses: + "204": + description: No Content + /api/representations/{id}/requests: + get: + tags: + - Representations + summary: Get all children of a base resource with pagination + operationId: getResource_4 + parameters: - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page in: query required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRequestedResourceView' + put: + tags: + - Representations + summary: Replace the children of a base resource + operationId: replaceResources_4 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Representations + summary: Add a list of children to a base resource + operationId: addResources_4 + parameters: + - name: id + in: path + required: true schema: type: string format: uuid @@ -606,186 +795,3471 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceRepresentation' + type: array + items: + type: string + format: uri required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/{resource-id}/{representation-id}/data: + $ref: '#/components/schemas/PagedModelRequestedResourceView' + delete: + tags: + - Representations + summary: Remove a list of children from a base resource + operationId: removeResources_4 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/representations/{id}/offers: get: tags: - - 'Backend: Resource Data Handling' - summary: Request Data String by Representation - description: Get the resource's data as a string by representation. - operationId: getDataByRepresentation + - Representations + summary: Get all children of a base resource with pagination + operationId: getResource_5 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + put: + tags: + - Representations + summary: Replace the children of a base resource + operationId: replaceResources_5 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - example: a4212311-86e4-40b3-ace3-ef29cd687cf9 - - name: representation-id + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Representations + summary: Add a list of children to a base resource + operationId: addResources_5 + parameters: + - name: id in: path - description: The representation uuid. required: true schema: type: string format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/{resource-id}/data: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + delete: + tags: + - Representations + summary: Remove a list of children from a base resource + operationId: removeResources_5 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/representations/{id}/artifacts: get: tags: - - 'Backend: Resource Data Handling' - summary: Request Data String - description: Get the resource's data as a string. - operationId: getDataById + - Representations + summary: Get all children of a base resource with pagination + operationId: getResource_6 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - example: a4212311-86e4-40b3-ace3-ef29cd687cf9 + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + $ref: '#/components/schemas/PagedModelArtifactView' put: tags: - - 'Backend: Resource Data Handling' - summary: Publish Resource Data String - description: Publish resource data as string. - operationId: publishResource + - Representations + summary: Replace the children of a base resource + operationId: replaceResources_6 parameters: - - name: resource-id + - name: id in: path - description: The resource uuid. required: true schema: type: string format: uuid - example: a4212311-86e4-40b3-ace3-ef29cd687cf9 - - name: data - in: query - description: The resource data. + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Representations + summary: Add a list of children to a base resource + operationId: addResources_6 + parameters: + - name: id + in: path required: true schema: type: string - example: Data String + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object -components: - schemas: - BackendSource: - type: object - properties: - url: - type: string - format: uri - username: - type: string - password: - type: string - system: - type: string - description: Information of the backend system. - oneOf: - - $ref: '#/components/schemas/BackendSource' - ResourceRepresentation: - type: object - properties: - uuid: - type: string - format: uuid - type: - type: string - byteSize: - type: integer - format: int32 - sourceType: - type: string - description: Information of the backend system. - enum: - - local - - http-get - - http-get-basicauth - - https-get - - https-get-basicauth - - mongodb - source: - $ref: '#/components/schemas/BackendSource' - description: A new resource representation. - example: - type: json - byteSize: 105 - sourceType: http-get - source: - url: https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02 - username: "-" - password: "-" - system: Open Weather Map API - oneOf: - - $ref: '#/components/schemas/ResourceRepresentation' - ResourceMetadata: - required: - - representations - type: object - properties: - title: - type: string - description: - type: string - keywords: - type: array - items: + $ref: '#/components/schemas/PagedModelArtifactView' + delete: + tags: + - Representations + summary: Remove a list of children from a base resource + operationId: removeResources_6 + parameters: + - name: id + in: path + required: true + schema: type: string - policy: - type: string - owner: - type: string - format: uri - license: - type: string - format: uri - version: - type: string - representations: - type: array + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/offers/{id}: + get: + tags: + - Resources + summary: Get a base resource by id + operationId: get_3 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/OfferedResourceView' + put: + tags: + - Resources + summary: Update a base resource by id + operationId: update_3 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OfferedResourceDesc' + required: true + responses: + "204": + description: No Content + content: + '*/*': + schema: + type: object + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: + tags: + - Resources + summary: Delete a base resource by id + operationId: delete_3 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + /api/offers/{id}/representations: + get: + tags: + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_7 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRepresentationView' + put: + tags: + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_7 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Resources + summary: Add a list of children to a base resource + operationId: addResources_7 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRepresentationView' + delete: + tags: + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_7 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/offers/{id}/contracts: + get: + tags: + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_8 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelContractView' + put: + tags: + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_8 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Resources + summary: Add a list of children to a base resource + operationId: addResources_8 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelContractView' + delete: + tags: + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_8 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/offers/{id}/catalogs: + get: + tags: + - Resources + summary: Get all children of a base resource with pagination + operationId: getResource_9 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelCatalogView' + put: + tags: + - Resources + summary: Replace the children of a base resource + operationId: replaceResources_9 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Resources + summary: Add a list of children to a base resource + operationId: addResources_9 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelCatalogView' + delete: + tags: + - Resources + summary: Remove a list of children from a base resource + operationId: removeResources_9 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/contracts/{id}: + get: + tags: + - Contracts + summary: Get a base resource by id + operationId: get_4 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/ContractView' + put: + tags: + - Contracts + summary: Update a base resource by id + operationId: update_4 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContractDesc' + required: true + responses: + "204": + description: No Content + content: + '*/*': + schema: + type: object + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: + tags: + - Contracts + summary: Delete a base resource by id + operationId: delete_4 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + /api/contracts/{id}/rules: + get: + tags: + - Contracts + summary: Get all children of a base resource with pagination + operationId: getResource_10 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelContractRuleView' + put: + tags: + - Contracts + summary: Replace the children of a base resource + operationId: replaceResources_10 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Contracts + summary: Add a list of children to a base resource + operationId: addResources_10 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelContractRuleView' + delete: + tags: + - Contracts + summary: Remove a list of children from a base resource + operationId: removeResources_10 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/contracts/{id}/requests: + get: + tags: + - Contracts + summary: Get all children of a base resource with pagination + operationId: getResource_11 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRequestedResourceView' + put: + tags: + - Contracts + summary: Replace the children of a base resource + operationId: replaceResources_11 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Contracts + summary: Add a list of children to a base resource + operationId: addResources_11 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRequestedResourceView' + delete: + tags: + - Contracts + summary: Remove a list of children from a base resource + operationId: removeResources_11 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/contracts/{id}/offers: + get: + tags: + - Contracts + summary: Get all children of a base resource with pagination + operationId: getResource_12 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + put: + tags: + - Contracts + summary: Replace the children of a base resource + operationId: replaceResources_12 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Contracts + summary: Add a list of children to a base resource + operationId: addResources_12 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + delete: + tags: + - Contracts + summary: Remove a list of children from a base resource + operationId: removeResources_12 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/configuration: + get: + tags: + - Connector + summary: Get current configuration. + operationId: getConfiguration + responses: + "404": + description: Not found + content: + application/json: + schema: + type: object + "200": + description: Ok + content: + application/json: + schema: + type: object + put: + tags: + - Connector + summary: Update current configuration. + operationId: updateConfiguration + requestBody: + content: + application/json: + schema: + type: string + application/ld+json: + schema: + type: string + required: true + responses: + "500": + description: Internal server error + content: + application/ld+json: + schema: + type: object + "400": + description: Failed to deserialize. + content: + application/ld+json: + schema: + type: object + "415": + description: Wrong media type. + content: + application/ld+json: + schema: + type: object + "200": + description: Ok + content: + application/ld+json: + schema: + type: object + /api/configuration/pattern: + get: + tags: + - Usage Control + summary: Get pattern validation status + description: Return if unsupported patterns are ignored when requesting data. + operationId: getPatternStatus + responses: + "200": + description: Ok + content: + application/json: + schema: + type: object + properties: + empty: + type: boolean + additionalProperties: + type: object + put: + tags: + - Usage Control + summary: Allow unsupported patterns + description: Allow requesting data without policy enforcement if an unsupported + pattern is recognized. + operationId: setPatternStatus + parameters: + - name: status + in: query + required: true + schema: + type: boolean + responses: + "200": + description: Ok + content: + application/json: + schema: + type: object + properties: + empty: + type: boolean + additionalProperties: + type: object + /api/configuration/negotiation: + get: + tags: + - Usage Control + summary: Get contract negotiation status + operationId: getNegotiationStatus + responses: + "200": + description: Ok + content: + application/json: + schema: + type: object + properties: + empty: + type: boolean + additionalProperties: + type: object + put: + tags: + - Usage Control + summary: Set contract negotiation status + operationId: setNegotiationStatus + parameters: + - name: status + in: query + required: true + schema: + type: boolean + responses: + "200": + description: Ok + content: + application/json: + schema: + type: object + properties: + empty: + type: boolean + additionalProperties: + type: object + /api/catalogs/{id}: + get: + tags: + - Catalogs + summary: Get a base resource by id + operationId: get_5 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CatalogView' + put: + tags: + - Catalogs + summary: Update a base resource by id + operationId: update_5 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogDesc' + required: true + responses: + "204": + description: No Content + content: + '*/*': + schema: + type: object + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: + tags: + - Catalogs + summary: Delete a base resource by id + operationId: delete_5 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + /api/catalogs/{id}/offers: + get: + tags: + - Catalogs + summary: Get all children of a base resource with pagination + operationId: getResource_13 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + put: + tags: + - Catalogs + summary: Replace the children of a base resource + operationId: replaceResources_13 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Catalogs + summary: Add a list of children to a base resource + operationId: addResources_13 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelOfferedResourceView' + delete: + tags: + - Catalogs + summary: Remove a list of children from a base resource + operationId: removeResources_13 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/artifacts/{id}: + get: + tags: + - Artifacts + summary: Get a base resource by id + operationId: get_6 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/ArtifactView' + put: + tags: + - Artifacts + summary: Update a base resource by id + operationId: update_6 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ArtifactDesc' + required: true + responses: + "204": + description: No Content + content: + '*/*': + schema: + type: object + "201": + description: Created + content: + '*/*': + schema: + type: object + delete: + tags: + - Artifacts + summary: Delete a base resource by id + operationId: delete_6 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + /api/artifacts/{id}/representations: + get: + tags: + - Artifacts + summary: Get all children of a base resource with pagination + operationId: getResource_14 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRepresentationView' + put: + tags: + - Artifacts + summary: Replace the children of a base resource + operationId: replaceResources_14 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + post: + tags: + - Artifacts + summary: Add a list of children to a base resource + operationId: addResources_14 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelRepresentationView' + delete: + tags: + - Artifacts + summary: Remove a list of children from a base resource + operationId: removeResources_14 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uri + required: true + responses: + "204": + description: No content + /api/artifacts/{id}/data: + put: + tags: + - Artifacts + operationId: putData + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + '*/*': + schema: + type: array + items: + type: string + format: byte + required: true + responses: + "200": + description: OK + post: + tags: + - Artifacts + summary: Get data by artifact id with query input + operationId: getData + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QueryInput' + responses: + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/rules: + get: + tags: + - Rules + summary: Get a list of base resources with pagination + operationId: getAll + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelContractRuleView' + post: + tags: + - Rules + summary: Create a base resource + operationId: create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContractRuleDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/ContractRuleView' + /api/representations: + get: + tags: + - Representations + summary: Get a list of base resources with pagination + operationId: getAll_2 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelRepresentationView' + post: + tags: + - Representations + summary: Create a base resource + operationId: create_1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RepresentationDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/RepresentationView' + /api/offers: + get: + tags: + - Resources + summary: Get a list of base resources with pagination + operationId: getAll_3 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelOfferedResourceView' + post: + tags: + - Resources + summary: Create a base resource + operationId: create_2 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OfferedResourceDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/OfferedResourceView' + /api/ids/resource/update: + post: + tags: + - IDS Messages + summary: Resource update message + description: Can be used for registering or updating a resource at an IDS broker. + operationId: sendConnectorUpdateMessage + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + - name: resourceId + in: query + description: The resource id. + required: true + schema: + type: string + format: uri + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/resource/unavailable: + post: + tags: + - IDS Messages + summary: Resource unavailable message + description: Can be used for unregistering a resource at an IDS broker. + operationId: sendConnectorUpdateMessage_1 + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + - name: resourceId + in: query + description: The resource id. + required: true + schema: + type: string + format: uri + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/query: + post: + tags: + - IDS Messages + summary: Query message + description: Can be used for querying an IDS broker. + operationId: sendConnectorUpdateMessage_2 + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + format: uri + requestBody: + content: + application/json: + schema: + type: string + description: Database query (SparQL) + example: "SELECT ?subject ?predicate ?object\nFROM \n\ + WHERE {\n ?subject ?predicate ?object\n};" + required: true + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/description: + post: + tags: + - IDS Messages + summary: Send ids description request message + operationId: sendDescriptionRequestMessage + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + format: uri + - name: elementId + in: query + description: The id of the requested resource. + required: false + schema: + type: string + format: uri + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "417": + description: Expectation failed + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/contract: + post: + tags: + - IDS Messages + summary: Send ids description request message + operationId: sendContractRequestMessage + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + format: uri + - name: resourceIds + in: query + description: List of ids resource that should be requested. + required: true + schema: + type: array + items: + type: string + format: uri + - name: artifactIds + in: query + description: List of ids artifacts that should be requested. + required: true + schema: + type: array + items: + type: string + format: uri + - name: download + in: query + description: Indicates whether the connector should automatically download + data of an artifact. + required: true + schema: + type: boolean + requestBody: + content: + application/json: + schema: + type: array + description: List of ids rules with an artifact id as target. + items: + oneOf: + - $ref: '#/components/schemas/Permission' + - $ref: '#/components/schemas/Prohibition' + required: true + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "417": + description: Expectation failed + content: + '*/*': + schema: + type: object + "201": + description: Created + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/connector/update: + post: + tags: + - IDS Messages + summary: Connector update message + description: Can be used for registering or updating the connector at an IDS + broker. + operationId: sendConnectorUpdateMessage_3 + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/ids/connector/unavailable: + post: + tags: + - IDS Messages + summary: Connector unavailable message + description: Can be used for unregistering the connector at an IDS broker. + operationId: sendConnectorUpdateMessage_4 + parameters: + - name: recipient + in: query + description: The recipient url. + required: true + schema: + type: string + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: object + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/examples/validation: + post: + tags: + - Usage Control + summary: Get pattern of policy + description: Get the policy pattern represented by a given JSON string. + operationId: getPolicyPattern + requestBody: + content: + application/json: + schema: + type: string + description: The JSON string representing a policy + required: true + responses: + "500": + description: Internal server error + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/examples/policy: + post: + tags: + - Usage Control + summary: Get example policy + description: Get an example policy for a given policy pattern. + operationId: getExampleUsagePolicy + parameters: + - name: type + in: query + description: Selection of supported policy patterns. + required: true + schema: + type: string + enum: + - PROVIDE_ACCESS + - PROHIBIT_ACCESS + - N_TIMES_USAGE + - DURATION_USAGE + - USAGE_DURING_INTERVAL + - USAGE_UNTIL_DELETION + - USAGE_LOGGING + - USAGE_NOTIFICATION + - CONNECTOR_RESTRICTED_USAGE + responses: + "400": + description: Bad Request + content: + '*/*': + schema: + type: object + "200": + description: Ok + content: + '*/*': + schema: + type: object + /api/contracts: + get: + tags: + - Contracts + summary: Get a list of base resources with pagination + operationId: getAll_4 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelContractView' + post: + tags: + - Contracts + summary: Create a base resource + operationId: create_3 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContractDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/ContractView' + /api/catalogs: + get: + tags: + - Catalogs + summary: Get a list of base resources with pagination + operationId: getAll_5 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelCatalogView' + post: + tags: + - Catalogs + summary: Create a base resource + operationId: create_4 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/CatalogView' + /api/artifacts: + get: + tags: + - Artifacts + summary: Get a list of base resources with pagination + operationId: getAll_6 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelArtifactView' + post: + tags: + - Artifacts + summary: Create a base resource + operationId: create_5 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ArtifactDesc' + required: true + responses: + "201": + description: Created + content: + '*/*': + schema: + $ref: '#/components/schemas/ArtifactView' + /api/requests: + get: + tags: + - Resources + summary: Get a list of base resources with pagination + operationId: getAll_1 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelRequestedResourceView' + /api/connector: + get: + tags: + - Connector + summary: Private IDS self-description + operationId: getPrivateSelfDescription + responses: + "500": + description: Internal server error + content: + application/ld+json: + schema: + type: object + "200": + description: Ok + content: + application/ld+json: + schema: + type: object + /api/artifacts/{id}/data/**: + get: + tags: + - Artifacts + summary: Get data by artifact id with query input + operationId: getData_1 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: download + in: query + required: false + schema: + type: boolean + - name: agreementUri + in: query + required: false + schema: + type: string + format: uri + - name: params + in: query + required: true + schema: + type: object + additionalProperties: + type: string + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/StreamingResponseBody' + /api/artifacts/{id}/agreements: + get: + tags: + - Artifacts + summary: Get all children of a base resource with pagination + operationId: getResource_15 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelAgreementView' + /api/agreements: + get: + tags: + - Agreements + summary: Get a list of base resources with pagination + operationId: getAll_7 + parameters: + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/CollectionModelAgreementView' + /api/agreements/{id}: + get: + tags: + - Agreements + summary: Get a base resource by id + operationId: get_7 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/AgreementView' + /api/agreements/{id}/artifacts: + get: + tags: + - Agreements + summary: Get all children of a base resource with pagination + operationId: getResource_16 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 0 + - name: size + in: query + required: false + schema: + type: integer + format: int32 + default: 30 + responses: + "200": + description: Ok + content: + '*/*': + schema: + $ref: '#/components/schemas/PagedModelArtifactView' + /: + get: + tags: + - Connector + summary: Public IDS self-description + operationId: getPublicSelfDescription_1 + responses: + "200": + description: Ok + content: + application/ld+json: + schema: + type: object +components: + schemas: + ContractRuleDesc: + type: object + properties: + remoteId: + type: string + format: uri + readOnly: true + title: + type: string + value: + type: string + RequestedResourceDesc: + type: object + properties: + title: + type: string + description: + type: string + keywords: + type: array + items: + type: string + publisher: + type: string + format: uri + language: + type: string + licence: + type: string + format: uri + sovereign: + type: string + format: uri + endpointDocumentation: + type: string + format: uri + remoteId: + type: string + format: uri + readOnly: true + RepresentationDesc: + type: object + properties: + remoteId: + type: string + format: uri + readOnly: true + title: + type: string + mediaType: + type: string + language: + type: string + standard: + type: string + OfferedResourceDesc: + type: object + properties: + title: + type: string + description: + type: string + keywords: + type: array + items: + type: string + publisher: + type: string + format: uri + language: + type: string + licence: + type: string + format: uri + sovereign: + type: string + format: uri + endpointDocumentation: + type: string + format: uri + ContractDesc: + type: object + properties: + remoteId: + type: string + format: uri + readOnly: true + consumer: + type: string + format: uri + provider: + type: string + format: uri + title: + type: string + start: + type: string + format: date-time + end: + type: string + format: date-time + CatalogDesc: + type: object + properties: + title: + type: string + description: + type: string + ArtifactDesc: + type: object + properties: + remoteId: + type: string + format: uri + readOnly: true + remoteAddress: + type: string + format: uri + readOnly: true + title: + type: string + accessUrl: + type: string + format: url + username: + type: string + password: + type: string + value: + type: string + automatedDownload: + type: boolean + ContractRuleView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + title: + type: string + value: + type: string + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + Link: + type: object + properties: + rel: + type: string + href: + type: string + hreflang: + type: string + media: + type: string + title: + type: string + type: + type: string + deprecation: + type: string + profile: + type: string + name: + type: string + ContractView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + title: + type: string + start: + type: string + format: date-time + end: + type: string + format: date-time + consumer: + type: string + format: uri + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + PageMetadata: + type: object + properties: + size: + type: integer + format: int64 + totalElements: + type: integer + format: int64 + totalPages: + type: integer + format: int64 + number: + type: integer + format: int64 + PagedModelContractView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ContractView' + page: + $ref: '#/components/schemas/PageMetadata' + PagedModelRepresentationView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/RepresentationView' + page: + $ref: '#/components/schemas/PageMetadata' + RepresentationView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + remoteId: + type: string + format: uri + title: + type: string + mediaType: + type: string + language: + type: string + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + CatalogView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + title: + type: string + description: + type: string + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + PagedModelCatalogView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/CatalogView' + page: + $ref: '#/components/schemas/PageMetadata' + PagedModelRequestedResourceView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/RequestedResourceView' + page: + $ref: '#/components/schemas/PageMetadata' + RequestedResourceView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + remoteId: + type: string + format: uri + title: + type: string + description: + type: string + keywords: + type: array items: - $ref: '#/components/schemas/ResourceRepresentation' - description: Metadata of a resource - example: - title: Sample Resource - description: This is an example resource containing weather data. + type: string + publisher: + type: string + format: uri + language: + type: string + licence: + type: string + format: uri + version: + type: integer + format: int64 + sovereign: + type: string + format: uri + endpointDocumentation: + type: string + format: uri + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + OfferedResourceView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + title: + type: string + description: + type: string keywords: - - weather - - data - - sample - owner: https://openweathermap.org/ - license: http://opendatacommons.org/licenses/odbl/1.0/ - version: "1.0" + type: array + items: + type: string + publisher: + type: string + format: uri + language: + type: string + licence: + type: string + format: uri + version: + type: integer + format: int64 + sovereign: + type: string + format: uri + endpointDocumentation: + type: string + format: uri + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + PagedModelOfferedResourceView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/OfferedResourceView' + page: + $ref: '#/components/schemas/PageMetadata' + ArtifactView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + remoteId: + type: string + format: uri + title: + type: string + numAccessed: + type: integer + format: int64 + byteSize: + type: integer + format: int64 + checkSum: + type: integer + format: int64 + additional: + type: object + additionalProperties: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + PagedModelArtifactView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ArtifactView' + page: + $ref: '#/components/schemas/PageMetadata' + AbstractConstraint: + required: + - '@id' + - '@type' + type: object + properties: + properties: + type: object + additionalProperties: + type: object + comment: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + label: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + '@id': + type: string + format: uri + '@type': + type: string + discriminator: + propertyName: '@type' + Constraint: + required: + - '@id' + - '@type' + - ids:leftOperand + - ids:operator + type: object + discriminator: + propertyName: '@type' + allOf: + - $ref: '#/components/schemas/AbstractConstraint' + - type: object + properties: + ids:unit: + type: string + format: uri + ids:rightOperand: + $ref: '#/components/schemas/RdfResource' + ids:leftOperand: + type: string + enum: + - https://w3id.org/idsa/code/PAYMENT + - https://w3id.org/idsa/code/PAY_AMOUNT + - https://w3id.org/idsa/code/POLICY_EVALUATION_TIME + - https://w3id.org/idsa/code/ELAPSED_TIME + - https://w3id.org/idsa/code/DELAY + - https://w3id.org/idsa/code/EVENT + - https://w3id.org/idsa/code/STATE + - https://w3id.org/idsa/code/ABSOLUTE_SPATIAL_POSITION + - https://w3id.org/idsa/code/USER + - https://w3id.org/idsa/code/PURPOSE + - https://w3id.org/idsa/code/COUNT + - https://w3id.org/idsa/code/QUANTITY + - https://w3id.org/idsa/code/RECURRENCE_RATE + - https://w3id.org/idsa/code/SECURITY_LEVEL + - https://w3id.org/idsa/code/SYSTEM + - https://w3id.org/idsa/code/ENDPOINT + - https://w3id.org/idsa/code/PATH + ids:pipEndpoint: + type: string + format: uri + ids:operator: + type: string + enum: + - https://w3id.org/idsa/code/EQUALS + - https://w3id.org/idsa/code/SAME_AS + - https://w3id.org/idsa/code/NOT + - https://w3id.org/idsa/code/HAS_STATE + - https://w3id.org/idsa/code/GTEQ + - https://w3id.org/idsa/code/GT + - https://w3id.org/idsa/code/EQ + - https://w3id.org/idsa/code/LTEQ + - https://w3id.org/idsa/code/LT + - https://w3id.org/idsa/code/IN + - https://w3id.org/idsa/code/STRING_EQ + - https://w3id.org/idsa/code/STRING_CONTAINS + - https://w3id.org/idsa/code/STRING_IS_CONTAINED + - https://w3id.org/idsa/code/MATCHES + - https://w3id.org/idsa/code/AFTER + - https://w3id.org/idsa/code/BEFORE + - https://w3id.org/idsa/code/CONTAINS + - https://w3id.org/idsa/code/TEMPORAL_DISJOINT + - https://w3id.org/idsa/code/DURING + - https://w3id.org/idsa/code/TEMPORAL_EQUALS + - https://w3id.org/idsa/code/FINISHED_BY + - https://w3id.org/idsa/code/FINISHES + - https://w3id.org/idsa/code/MEETS + - https://w3id.org/idsa/code/MET_BY + - https://w3id.org/idsa/code/OVERLAPS + - https://w3id.org/idsa/code/OVERLAPPED_BY + - https://w3id.org/idsa/code/STARTS + - https://w3id.org/idsa/code/STARTED_BY + - https://w3id.org/idsa/code/DURATION_EQ + - https://w3id.org/idsa/code/LONGER + - https://w3id.org/idsa/code/LONGER_EQ + - https://w3id.org/idsa/code/SHORTER_EQ + - https://w3id.org/idsa/code/SHORTER + - https://w3id.org/idsa/code/COVERED_BY + - https://w3id.org/idsa/code/COVERS + - https://w3id.org/idsa/code/INSIDE + - https://w3id.org/idsa/code/SPATIAL_CONTAINS + - https://w3id.org/idsa/code/SPATIAL_EQUALS + - https://w3id.org/idsa/code/SPATIAL_OVERLAP + - https://w3id.org/idsa/code/SPATIAL_MEET + - https://w3id.org/idsa/code/DISJOINT + - https://w3id.org/idsa/code/MEMBER_OF + - https://w3id.org/idsa/code/HAS_MEMBERSHIP + - https://w3id.org/idsa/code/HAS_SITE + - https://w3id.org/idsa/code/INSIDE_NETWORK + - https://w3id.org/idsa/code/DEFINES_AS + ids:rightOperandReference: + type: string + format: uri + '@type': + type: string + Duty: + required: + - '@id' + - '@type' + - ids:action + type: object + properties: + properties: + type: object + additionalProperties: + type: object + comment: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + label: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + '@id': + type: string + format: uri + ids:target: + type: string + format: uri + ids:constraint: + type: array + items: + oneOf: + - $ref: '#/components/schemas/LogicalConstraint' + ids:assigner: + type: array + items: + type: string + format: uri + ids:assignee: + type: array + items: + type: string + format: uri + ids:action: + type: array + items: + type: string + enum: + - https://w3id.org/idsa/code/ANONYMIZE + - https://w3id.org/idsa/code/USE + - https://w3id.org/idsa/code/AGGREGATE_BY_CONSUMER + - https://w3id.org/idsa/code/AGGREGATE_BY_PROVIDER + - https://w3id.org/idsa/code/COMPENSATE + - https://w3id.org/idsa/code/DELETE + - https://w3id.org/idsa/code/WRITE + - https://w3id.org/idsa/code/DISTRIBUTE + - https://w3id.org/idsa/code/GRANT_USE + - https://w3id.org/idsa/code/ENCRYPT + - https://w3id.org/idsa/code/LOG + - https://w3id.org/idsa/code/MODIFY + - https://w3id.org/idsa/code/NEXT_POLICY + - https://w3id.org/idsa/code/NOTIFY + - https://w3id.org/idsa/code/READ + - https://w3id.org/idsa/code/TRACK_PROVENANCE + ids:assetRefinement: + oneOf: + - $ref: '#/components/schemas/Constraint' + - $ref: '#/components/schemas/LogicalConstraint' + ids:description: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + ids:title: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + '@type': + type: string + discriminator: + propertyName: '@type' + LogicalConstraint: + required: + - '@id' + - '@type' + type: object + discriminator: + propertyName: '@type' + allOf: + - $ref: '#/components/schemas/AbstractConstraint' + - type: object + properties: + ids:or: + type: array + items: + $ref: '#/components/schemas/Constraint' + ids:xone: + type: array + items: + $ref: '#/components/schemas/Constraint' + ids:and: + type: array + items: + $ref: '#/components/schemas/Constraint' + '@type': + type: string + Permission: + required: + - '@id' + - '@type' + - ids:action + type: object + discriminator: + propertyName: '@type' + allOf: + - $ref: '#/components/schemas/Rule' + - type: object + properties: + ids:preDuty: + type: array + items: + $ref: '#/components/schemas/Duty' + ids:postDuty: + type: array + items: + $ref: '#/components/schemas/Duty' + '@type': + type: string + Prohibition: + required: + - '@id' + - '@type' + - ids:action + type: object + discriminator: + propertyName: '@type' + allOf: + - $ref: '#/components/schemas/Rule' + - type: object + properties: + '@type': + type: string + RdfResource: + type: object + properties: + '@value': + type: string + '@type': + type: string + Rule: + required: + - '@id' + - '@type' + - ids:action + type: object + properties: + properties: + type: object + additionalProperties: + type: object + comment: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + label: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + '@id': + type: string + format: uri + ids:target: + type: string + format: uri + ids:constraint: + type: array + items: + oneOf: + - $ref: '#/components/schemas/LogicalConstraint' + ids:assigner: + type: array + items: + type: string + format: uri + ids:assignee: + type: array + items: + type: string + format: uri + ids:action: + type: array + items: + type: string + enum: + - https://w3id.org/idsa/code/ANONYMIZE + - https://w3id.org/idsa/code/USE + - https://w3id.org/idsa/code/AGGREGATE_BY_CONSUMER + - https://w3id.org/idsa/code/AGGREGATE_BY_PROVIDER + - https://w3id.org/idsa/code/COMPENSATE + - https://w3id.org/idsa/code/DELETE + - https://w3id.org/idsa/code/WRITE + - https://w3id.org/idsa/code/DISTRIBUTE + - https://w3id.org/idsa/code/GRANT_USE + - https://w3id.org/idsa/code/ENCRYPT + - https://w3id.org/idsa/code/LOG + - https://w3id.org/idsa/code/MODIFY + - https://w3id.org/idsa/code/NEXT_POLICY + - https://w3id.org/idsa/code/NOTIFY + - https://w3id.org/idsa/code/READ + - https://w3id.org/idsa/code/TRACK_PROVENANCE + ids:assetRefinement: + oneOf: + - $ref: '#/components/schemas/Constraint' + - $ref: '#/components/schemas/LogicalConstraint' + ids:description: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + ids:title: + type: array + items: + $ref: '#/components/schemas/TypedLiteral' + '@type': + type: string + discriminator: + propertyName: '@type' + TypedLiteral: + type: object + properties: + '@value': + type: string + '@type': + type: string + '@language': + type: string + PagedModelContractRuleView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ContractRuleView' + page: + $ref: '#/components/schemas/PageMetadata' + QueryInput: + type: object + properties: + headers: + type: object + additionalProperties: + type: string + params: + type: object + additionalProperties: + type: string + pathVariables: + type: object + additionalProperties: + type: string + optional: + type: string + description: "Query parameters, headers and path variables as maps" + example: + headers: + key: value + params: + key: value + pathVariables: + key: value oneOf: - - $ref: '#/components/schemas/ResourceMetadata' + - $ref: '#/components/schemas/QueryInput' + CollectionModelContractRuleView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ContractRuleView' + CollectionModelRequestedResourceView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/RequestedResourceView' + CollectionModelRepresentationView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/RepresentationView' + CollectionModelOfferedResourceView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/OfferedResourceView' + CollectionModelContractView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ContractView' + CollectionModelCatalogView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/CatalogView' + CollectionModelArtifactView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/ArtifactView' + StreamingResponseBody: + type: object + AgreementView: + type: object + properties: + creationDate: + type: string + format: date-time + modificationDate: + type: string + format: date-time + remoteId: + type: string + format: uri + confirmed: + type: boolean + value: + type: string + links: + type: array + items: + $ref: '#/components/schemas/Link' + PagedModelAgreementView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/AgreementView' + page: + $ref: '#/components/schemas/PageMetadata' + CollectionModelAgreementView: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/Link' + content: + type: array + items: + $ref: '#/components/schemas/AgreementView' diff --git a/pom.xml b/pom.xml index c0abf3e72..b7a340a15 100644 --- a/pom.xml +++ b/pom.xml @@ -1,27 +1,52 @@ - + + 4.0.0 + org.springframework.boot spring-boot-starter-parent - 2.2.7.RELEASE - + 2.4.5 + + - de.fraunhofer.isst - dataspace-connector - ${project.version} - dataspace-connector - IDS Connector developed by the Fraunhofer ISST - https://gitlab.cc-asp.fraunhofer.de/fhg-isst-ids/dataspace-connector + + io + dataspaceconnector + ${revision} + + Dataspace Connector + IDS Connector originally developed by the Fraunhofer ISST + https://www.dataspace-connector.io/ + 2020 Fraunhofer Institute for Software and Systems Engineering https://www.isst.fraunhofer.de/ - - Gitlab - https://gitlab.cc-asp.fraunhofer.de/fhg-isst-ids/dataspace-connector/-/issues - + + + ${licence_name} + ${licence_url} + ${licence_distribution} + + + Julia Pampus @@ -29,8 +54,7 @@ Fraunhofer Institute for Software and Systems Engineering https://www.isst.fraunhofer.de/ - head - architect + lead developer @@ -43,121 +67,227 @@ developer + + Brian-Frederik Jahnke + brian-frederik.jahnke@isst.fraunhofer.de + Fraunhofer Institute for Software and Systems Engineering + https://www.isst.fraunhofer.de/ + + developer + + - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - 2020 + + + Github + https://github.com/FraunhoferISST/DataspaceConnector/issues + - 3.2.1 - 3.2.3 + + 5.0.0 + info@dataspace-connector.de + 11 - ${java.version} - ${java.version} + ${java.version} 3.3.9 - 3.0.0-M2 - 2.2.0.RELEASE - 20190722 - 1.5.20 - 4.2.2 + + 4.0.7 + + 2.0.0.RELEASE + 1.6.2 + 4.9.1 + 2.4.3 + 2.12.3 + 42.2.20 + 1.5.8 + 1.5.8 + 0.13.3 + 3.6 + + + 3.0.0-M3 + 3.2.0 + 3.0.0-M5 + 1.6.6 + 0.14 + 1.1.0 + 3.9.1 + 3.1.2 + 4.2.3 + 1.11.0 + 3.1.2 + 8.42 + 0.8.7 + 3.14.0 + 2.8.1 + 4.1 + 2.2.0 + 2.4 + 3.1.1 + 2.0 + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo de.fraunhofer.isst.ids.framework - spring-starter - ${framework.version} + base + ${ids-framework.version} + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + de.fraunhofer.iais.eis.ids.infomodel + java + + - - org.springframework.boot - spring-boot-starter + de.fraunhofer.iais.eis.ids.infomodel + java + 4.0.4 + + + + de.fraunhofer.isst.ids.framework + messaging + ${ids-framework.version} - ch.qos.logback - logback-classic + org.slf4j + slf4j-log4j12 + + + log4j + log4j + org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-actuator - com.vaadin.external.google - android-json + org.springframework.boot + spring-boot-starter-logging + - org.springframework.boot - spring-boot-starter-web + org.springframework.plugin + spring-plugin-core + ${springframework.plugin} org.springframework.boot spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-logging + + org.springframework.boot - spring-boot-starter-security + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + - org.springframework.security - spring-security-test + org.springframework.boot + spring-boot-starter-test + test + + + com.vaadin.external.google + android-json + + + org.springframework.boot + spring-boot-starter-logging + + org.springframework.boot - spring-boot-devtools + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + - - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter.version} + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-logging + + + - org.junit.jupiter - junit-jupiter-engine - ${junit-jupiter.version} + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-starter-logging + + - org.junit.jupiter - junit-jupiter-params - ${junit-jupiter.version} + org.springframework.security + spring-security-test + test - - org.json - json - ${org.json.version} + org.springframework.boot + spring-boot-devtools + true - - org.slf4j - slf4j-api + org.springframework.boot + spring-boot-starter-log4j2 + io.swagger swagger-annotations @@ -170,6 +300,13 @@ ${lombok.version} + + + org.modelmapper + modelmapper + ${modelmapper.version} + + com.squareup.okhttp3 @@ -180,45 +317,154 @@ com.fasterxml.jackson.core jackson-databind - 2.10.0.pr2 + ${jackson.version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} org.postgresql postgresql - 42.2.5 + ${postgres.version} + runtime + + org.springdoc springdoc-openapi-ui - 1.4.1 + ${springdoc-ui.version} org.springdoc springdoc-openapi-security - 1.4.1 + ${springdoc-security.version} com.github.jsonld-java jsonld-java - 0.13.0 + ${jsonld-java.version} + + + + nl.jqno.equalsverifier + equalsverifier + ${equalsverifier.version} + test - isst-nexus-public + Maven repository + https://repo1.maven.org/maven2/ + + false + + + true + + + + ids-public isst-public https://mvn.ids.isst.fraunhofer.de/nexus/repository/ids-public/ + + false + + + true + + + + eis-ids-public + maven-snapshots + https://maven.iais.fraunhofer.de/artifactory/eis-ids-public + + false + + + true + + + + src/main/resources + true + + conf/**/*.* + log4j2.xml + application.properties + banner.txt + + + + **/*.p12 + + + + src/main/resources + false + + **/*.p12 + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + scripts/ci/checkstyle.xml + UTF-8 + true + true + + + + validate + validate + + check + + + + + + maven-dependency-plugin @@ -251,7 +497,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + ${maven-javadoc-plugin.version} private true @@ -271,18 +517,348 @@ + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + licence_type + licence_url + licence_distribution + ${project.build.directory} + + + + + com.mycila + license-maven-plugin + ${license-plugin.version} + + + +
scripts/ci/license/header.txt
+ + **/README + src/test/resources/** + src/main/resources/** + **/*.env + **/lombok.config + **/.dockerignore + docs/** + +
+
+ + ${project.inceptionYear} + +
+ + + + check + + + +
+ + org.pitest + pitest-maven + ${pitest.version} + + + org.pitest + pitest-junit5-plugin + ${pitest-junit5-plugin.version} + + + + + io.dataspaceconnector* + + + io.dataspaceconnector* + + + ** + + + ALL + + ${project.build.directory}/pit-reports/history + ${project.build.directory}/pit-reports/history + false + 4 + false + + + + + verify + verify + + mutationCoverage + + + + + + org.codehaus.mojo + tidy-maven-plugin + ${tidy.version} + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + + org.apache.maven.plugins + maven-project-info-reports-plugin + ${maven-project-info-reports-plugin.version} + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + default-prepare-agent + + prepare-agent + + + + default-prepare-agent-integration + + prepare-agent-integration + + + + default-report + + report + + + + default-report-integration + + report-integration + + + + default-check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 25% + + + + + + + +
- - - - src/main/resources - false - - conf/**/*.* - log4j.xml - application.properties - - -
+ + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + Max + Low + true + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs-plugin.version} + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + checkstyle + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.version} + + + true + + /category/java/bestpractices.xml + /category/java/codestyle.xml + /category/java/design.xml + /category/java/documentation.xml + /category/java/errorprone.xml + /category/java/multithreading.xml + /category/java/performance.xml + /category/java/security.xml + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + report + + + + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + + + dependency-updates-report + plugin-updates-report + property-updates-report + + + + + + com.github.sevntu-checkstyle + dsm-maven-plugin + ${dms.version} + + + org.codehaus.mojo + taglist-maven-plugin + ${taglist.version} + + + + + TODO + + + TODO + exact + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + ${jxr.version} + + + org.codehaus.mojo + jdepend-maven-plugin + ${jdepend.version} + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin.version} + + + org.pitest + pitest-maven + ${pitest.version} + + + + report + + + + + + + + + + + + no-tests + + true + + + + + + no-documentation + + true + + + + + + + release + + + false + false + + + + + maven-compiler-plugin + + true + true + true + true + + + -Xlint:all + -Xlint:-processing + + + + + + +
diff --git a/postgres-configmap.yaml b/postgres-configmap.yaml new file mode 100644 index 000000000..9a3f4a469 --- /dev/null +++ b/postgres-configmap.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config + labels: + app: postgres +data: + POSTGRES_DB: test + POSTGRES_USER: admin + POSTGRES_PASSWORD: password diff --git a/postgres-deployment.yaml b/postgres-deployment.yaml new file mode 100644 index 000000000..bdcc4d99f --- /dev/null +++ b/postgres-deployment.yaml @@ -0,0 +1,39 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:12.5 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5432 + envFrom: + - configMapRef: + name: postgres-config diff --git a/postgres-service.yaml b/postgres-service.yaml new file mode 100644 index 000000000..91e4f1d24 --- /dev/null +++ b/postgres-service.yaml @@ -0,0 +1,27 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: postgres + labels: + app: postgres +spec: + ports: + - port: 5432 + selector: + app: postgres diff --git a/scripts/ci/checkstyle.xml b/scripts/ci/checkstyle.xml new file mode 100644 index 000000000..adce40f69 --- /dev/null +++ b/scripts/ci/checkstyle.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/ci/license/header.txt b/scripts/ci/license/header.txt new file mode 100644 index 000000000..6480861b4 --- /dev/null +++ b/scripts/ci/license/header.txt @@ -0,0 +1,13 @@ +Copyright ${year} ${project.organization.name} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/tests/contract_negotation_allow_access.py b/scripts/tests/contract_negotation_allow_access.py new file mode 100644 index 000000000..ad4290df4 --- /dev/null +++ b/scripts/tests/contract_negotation_allow_access.py @@ -0,0 +1,152 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import requests +import pprint +import json +import tqdm + +# Suppress ssl verification warning +requests.packages.urllib3.disable_warnings() + +s = requests.Session() +s.auth = ('admin', 'password') +s.verify = False + +def create_catalog(): + response = s.post("https://localhost:8080/api/catalogs", json={}) + return response.headers['Location'] + + +def create_offered_resource(): + response = s.post("https://localhost:8080/api/offers", json={}) + return response.headers['Location'] + + +def create_representation(): + response = s.post("https://localhost:8080/api/representations", json={}) + return response.headers['Location'] + + +def create_artifact(): + response = s.post("https://localhost:8080/api/artifacts", json={"value": "SOME LONG VALUE"}) + return response.headers['Location'] + + +def create_contract(): + response = s.post("https://localhost:8080/api/contracts", json={'start': '2021-04-06T13:33:44.995+02:00', 'end':'2021-12-06T13:33:44.995+02:00'}) + return response.headers['Location'] + + +def create_rule_allow_access(): + response = s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/cf1cb758-b96d-4486-b0a7-f3ac0e289588", + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:description": [ + { + "@value": "provide-access", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ] + }"""}) + return response.headers['Location'] + + +def add_resource_to_catalog(catalog, resource): + response = s.post(catalog + "/offers", json=[resource]) + +def add_catalog_to_resource(resource, catalog): + response = s.post(resource + "/catalogs", json=[catalog]) + + +def add_representation_to_resource(resource, representation): + response = s.post(resource + "/representations", + json=[representation]) + + +def add_artifact_to_representation(representation, artifact): + response = s.post(representation + "/artifacts", + json=[artifact]) + + +def add_contract_to_resource(resource, contract): + response = s.post(resource + "/contracts", json=[contract]) + +def add_rule_to_contract(contract, rule): + response = s.post(contract + "/rules", json=[rule]) + + +# IDS +def descriptionRequest(recipient, elementId): + url = "https://localhost:8080/api/ids/description" + params = {} + if recipient is not None: + params['recipient'] = recipient + if elementId is not None: + params['elementId'] = elementId + + return s.post(url, params=params) + + +def contractRequest(recipient, resourceId, artifactId, download, contract): + url = "https://localhost:8080/api/ids/contract" + params = {} + if recipient is not None: + params['recipient'] = recipient + if resourceId is not None: + params['resourceIds'] = resourceId + if artifactId is not None: + params['artifactIds'] = artifactId + if download is not None: + params['download'] = download + + return s.post(url, params=params, json=[contract]) + +# Create resources +catalog = create_catalog() +offers = create_offered_resource() +representation = create_representation() +artifact = create_artifact() +contract = create_contract() +use_rule = create_rule_allow_access() + +# Link resources +add_resource_to_catalog(catalog, offers) +add_representation_to_resource(offers, representation) +add_artifact_to_representation(representation, artifact) +add_contract_to_resource(offers, contract) +add_rule_to_contract(contract, use_rule) + +# Call description +response = descriptionRequest("https://localhost:8080/api/ids/data", offers) +offer = json.loads(response.text) + +# Negotiate contract +obj = offer['ids:contractOffer'][0]['ids:permission'][0] +obj['ids:target'] = artifact +response = contractRequest("https://localhost:8080/api/ids/data", offers, artifact, False, obj) +pprint.pprint(str(response.content)) diff --git a/scripts/tests/provider_consumer_multiple_artifacts.py b/scripts/tests/provider_consumer_multiple_artifacts.py new file mode 100644 index 000000000..f3bea68f3 --- /dev/null +++ b/scripts/tests/provider_consumer_multiple_artifacts.py @@ -0,0 +1,249 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import requests +import pprint +import json +import tqdm +import os + +# Suppress ssl verification warning +requests.packages.urllib3.disable_warnings() + +s = requests.Session() +s.auth = ('admin', 'password') +s.verify = False + +#################################################################################################### +# PROVIDER (running on port 8080) # +#################################################################################################### + +def create_catalog(): + return s.post("https://localhost:8080/api/catalogs", json={}).headers['Location'] + +def create_offered_resource(): + return s.post("https://localhost:8080/api/offers", json={}).headers['Location'] + +def create_representation(): + return s.post("https://localhost:8080/api/representations", json={}).headers['Location'] + +def create_local_artifact(): + return s.post("https://localhost:8080/api/artifacts", json={"value": "SOME LONG VALUE"}).headers['Location'] + +def create_remote_artifact(): + return s.post("https://localhost:8080/api/artifacts", json={"accessUrl": "https://www.google.de/"}).headers['Location'] + +def create_contract(): + return s.post("https://localhost:8080/api/contracts", json={}).headers['Location'] + +def create_usage_notification_rule(): + return s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/c0bdb9d5-e86a-4bb3-86d2-2b1dc9d226f5", + "ids:description": [ + { + "@value": "usage-notification", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:postDuty": [ + { + "@type": "ids:Duty", + "@id": "https://w3id.org/idsa/autogen/duty/863d2fac-1072-476d-b504-9d6347fe4b6f", + "ids:action": [ + { + "@id": "idsc:NOTIFY" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/c91e64ce-1fc1-44fd-bec1-6c6778603919", + "ids:rightOperand": { + "@value": "https://localhost:8080/api/ids/data", + "@type": "xsd:anyURI" + }, + "ids:leftOperand": { + "@id": "idsc:ENDPOINT" + }, + "ids:operator": { + "@id": "idsc:DEFINES_AS" + } + } + ] + } + ] + }"""}).headers['Location'] + +def create_n_times_usage_rule(): + return s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/154df1cf-557b-4f44-b839-4b68056606a2", + "ids:description": [ + { + "@value": "n-times-usage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/4ae656d1-2a73-44e3-a168-b1cbe49d4622", + "ids:rightOperand": { + "@value": "5", + "@type": "xsd:double" + }, + "ids:leftOperand": { + "@id": "idsc:COUNT" + }, + "ids:operator": { + "@id": "idsc:LTEQ" + } + } + ] + }"""}).headers['Location'] + +def add_resource_to_catalog(catalog, resource): + s.post(catalog + "/offers", json=[resource]) + +def add_catalog_to_resource(resource, catalog): + s.post(resource + "/catalogs", json=[catalog]) + +def add_representation_to_resource(resource, representation): + s.post(resource + "/representations", json=[representation]) + +def add_artifact_to_representation(representation, artifact): + s.post(representation + "/artifacts", json=[artifact]) + +def add_contract_to_resource(resource, contract): + s.post(resource + "/contracts", json=[contract]) + +def add_rule_to_contract(contract, rule): + s.post(contract + "/rules", json=[rule]) + +# for i in tqdm.tqdm(range(500)): +catalog = create_catalog() +offer1 = create_offered_resource() +representation1 = create_representation() +artifact1 = create_local_artifact() +contract1 = create_contract() +notification_rule = create_usage_notification_rule() + +add_resource_to_catalog(catalog, offer1) +add_representation_to_resource(offer1, representation1) +add_artifact_to_representation(representation1, artifact1) +add_contract_to_resource(offer1, contract1) +add_rule_to_contract(contract1, notification_rule) + +offer2 = create_offered_resource() +representation2 = create_representation() +artifact2 = create_remote_artifact() +contract2 = create_contract() +count_rule = create_n_times_usage_rule() + +add_resource_to_catalog(catalog, offer2) +add_representation_to_resource(offer2, representation2) +add_artifact_to_representation(representation2, artifact2) +add_contract_to_resource(offer2, contract2) +add_rule_to_contract(contract2, count_rule) + +#################################################################################################### +# CONSUMER (running on port 8081) # +#################################################################################################### + +provider = "https://localhost:8080/api/ids/data" + +def descriptionRequest(recipient, elementId): + params = {} + if recipient is not None: + params['recipient'] = recipient + if elementId is not None: + params['elementId'] = elementId + + return s.post("https://localhost:8081/api/ids/description", params=params) + +def contractRequest(recipient, resourceId, artifactId, download, contract): + params = {} + if recipient is not None: + params['recipient'] = recipient + if resourceId is not None: + params['resourceIds'] = resourceId + if artifactId is not None: + params['artifactIds'] = artifactId + if download is not None: + params['download'] = download + + return s.post("https://localhost:8081/api/ids/contract", params=params, json=contract) + +response = descriptionRequest(provider, catalog) +catalogResponse = json.loads(response.text) + +obj = catalogResponse['ids:offeredResource'][0] +resourceId1 = obj['@id'] +contract = obj['ids:contractOffer'][0] +contractId = contract['@id'] +representation = obj['ids:representation'][0] +artifact = representation['ids:instance'][0] +artifactId1 = artifact['@id'] + +response = descriptionRequest(provider, contractId) +contract1Response = json.loads(response.text) + +obj = catalogResponse['ids:offeredResource'][1] +resourceId2 = obj['@id'] +contract = obj['ids:contractOffer'][0] +contractId = contract['@id'] +representation = obj['ids:representation'][0] +artifact = representation['ids:instance'][0] +artifactId2 = artifact['@id'] + +response = descriptionRequest(provider, contractId) +contract2Response = json.loads(response.text) + +notify = contract1Response['ids:permission'][0] +notify['ids:target'] = artifactId1 + +count = contract2Response['ids:permission'][0] +count['ids:target'] = artifactId2 + +# Accept both rules +body = [notify, count] +resources = [resourceId1, resourceId2] +artifacts = [artifactId1, artifactId2] +response = contractRequest(provider, resources, artifacts, True, body) +pprint.pprint(str(response.content)) diff --git a/scripts/tests/provider_consumer_single_artifact_multiple_policies.py b/scripts/tests/provider_consumer_single_artifact_multiple_policies.py new file mode 100644 index 000000000..b0fe28c9c --- /dev/null +++ b/scripts/tests/provider_consumer_single_artifact_multiple_policies.py @@ -0,0 +1,228 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import requests +import pprint +import json + +# Suppress ssl verification warning +requests.packages.urllib3.disable_warnings() + +s = requests.Session() +s.auth = ('admin', 'password') +s.verify = False + +#################################################################################################### +# PROVIDER (running on port 8080) # +#################################################################################################### + +def create_catalog(): + return s.post("https://localhost:8080/api/catalogs", json={}).headers['Location'] + +def create_offered_resource(): + return s.post("https://localhost:8080/api/offers", json={}).headers['Location'] + +def create_representation(): + return s.post("https://localhost:8080/api/representations", json={}).headers['Location'] + +def create_artifact(): + return s.post("https://localhost:8080/api/artifacts", json={"value": "SOME LONG VALUE"}).headers['Location'] + +def create_contract(): + return s.post("https://localhost:8080/api/contracts", json={}).headers['Location'] + +def create_usage_notification_rule(): + return s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/c0bdb9d5-e86a-4bb3-86d2-2b1dc9d226f5", + "ids:description": [ + { + "@value": "usage-notification", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:postDuty": [ + { + "@type": "ids:Duty", + "@id": "https://w3id.org/idsa/autogen/duty/863d2fac-1072-476d-b504-9d6347fe4b6f", + "ids:action": [ + { + "@id": "idsc:NOTIFY" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/c91e64ce-1fc1-44fd-bec1-6c6778603919", + "ids:rightOperand": { + "@value": "https://localhost:8080/api/ids/data", + "@type": "xsd:anyURI" + }, + "ids:leftOperand": { + "@id": "idsc:ENDPOINT" + }, + "ids:operator": { + "@id": "idsc:DEFINES_AS" + } + } + ] + } + ] + }"""}).headers['Location'] + +def create_n_times_usage_rule(): + return s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/154df1cf-557b-4f44-b839-4b68056606a2", + "ids:description": [ + { + "@value": "n-times-usage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:constraint": [ + { + "@type": "ids:Constraint", + "@id": "https://w3id.org/idsa/autogen/constraint/4ae656d1-2a73-44e3-a168-b1cbe49d4622", + "ids:rightOperand": { + "@value": "5", + "@type": "xsd:double" + }, + "ids:leftOperand": { + "@id": "idsc:COUNT" + }, + "ids:operator": { + "@id": "idsc:LTEQ" + } + } + ] + }"""}).headers['Location'] + +def add_resource_to_catalog(catalog, resource): + s.post(catalog + "/offers", json=[resource]) + +def add_catalog_to_resource(resource, catalog): + s.post(resource + "/catalogs", json=[catalog]) + +def add_representation_to_resource(resource, representation): + s.post(resource + "/representations", json=[representation]) + +def add_artifact_to_representation(representation, artifact): + s.post(representation + "/artifacts", json=[artifact]) + +def add_contract_to_resource(resource, contract): + s.post(resource + "/contracts", json=[contract]) + +def add_rule_to_contract(contract, rule): + s.post(contract + "/rules", json=[rule]) + +# for i in tqdm.tqdm(range(500)): +catalog = create_catalog() +offers = create_offered_resource() +representation = create_representation() +artifact = create_artifact() +contract = create_contract() +notification_rule = create_usage_notification_rule() +count_rule = create_n_times_usage_rule() + +add_resource_to_catalog(catalog, offers) +add_representation_to_resource(offers, representation) +add_artifact_to_representation(representation, artifact) +add_contract_to_resource(offers, contract) +add_rule_to_contract(contract, notification_rule) +add_rule_to_contract(contract, count_rule) + +#################################################################################################### +# CONSUMER (running on port 8081) # +#################################################################################################### + +provider = "https://localhost:8080/api/ids/data" + +def descriptionRequest(recipient, elementId): + params = {} + if recipient is not None: + params['recipient'] = recipient + if elementId is not None: + params['elementId'] = elementId + + return s.post("https://localhost:8081/api/ids/description", params=params) + +def contractRequest(recipient, resourceId, artifactId, download, contract): + params = {} + if recipient is not None: + params['recipient'] = recipient + if resourceId is not None: + params['resourceIds'] = resourceId + if artifactId is not None: + params['artifactIds'] = artifactId + if download is not None: + params['download'] = download + + return s.post("https://localhost:8081/api/ids/contract", params=params, json=contract) + +response = descriptionRequest(provider, catalog) +catalogResponse = json.loads(response.text) + +obj = catalogResponse['ids:offeredResource'][0] +resourceId = obj['@id'] +pprint.pprint(resourceId) +contract = obj['ids:contractOffer'][0] +contractId = contract['@id'] +pprint.pprint(contractId) +representation = obj['ids:representation'][0] +artifact = representation['ids:instance'][0] +artifactId = artifact['@id'] +pprint.pprint(artifactId) + +response = descriptionRequest(provider, contractId) +contractResponse = json.loads(response.text) + +notify = contractResponse['ids:permission'][0] +notify['ids:target'] = artifactId + +count = contractResponse['ids:permission'][1] +count['ids:target'] = artifactId + +# Only accept one rule -> causes a ContractRejectionMessage +# response = contractRequest(provider, resourceId, artifactId, True, notify) +# pprint.pprint(str(response.content)) + +# Accept both rules +body = [notify, count] +response = contractRequest(provider, resourceId, artifactId, True, body) +pprint.pprint(str(response.content)) diff --git a/scripts/tests/provider_consumer_single_artifact_single_policy.py b/scripts/tests/provider_consumer_single_artifact_single_policy.py new file mode 100644 index 000000000..fce3eae25 --- /dev/null +++ b/scripts/tests/provider_consumer_single_artifact_single_policy.py @@ -0,0 +1,151 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import requests +import pprint +import json + +# Suppress ssl verification warning +requests.packages.urllib3.disable_warnings() + +s = requests.Session() +s.auth = ('admin', 'password') +s.verify = False + +#################################################################################################### +# PROVIDER (running on port 8080) # +#################################################################################################### + +def create_catalog(): + return s.post("https://localhost:8080/api/catalogs", json={}).headers['Location'] + +def create_offered_resource(): + return s.post("https://localhost:8080/api/offers", json={}).headers['Location'] + +def create_representation(): + return s.post("https://localhost:8080/api/representations", json={}).headers['Location'] + +def create_artifact(): + return s.post("https://localhost:8080/api/artifacts", json={"value": "SOME LONG VALUE"}).headers['Location'] + +def create_contract(): + return s.post("https://localhost:8080/api/contracts", json={}).headers['Location'] + +def create_rule(): + return s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/cf1cb758-b96d-4486-b0a7-f3ac0e289588", + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:description": [ + { + "@value": "provide-access", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ] + }"""}).headers['Location'] + +def add_resource_to_catalog(catalog, resource): + s.post(catalog + "/offers", json=[resource]) + +def add_catalog_to_resource(resource, catalog): + s.post(resource + "/catalogs", json=[catalog]) + +def add_representation_to_resource(resource, representation): + s.post(resource + "/representations", json=[representation]) + +def add_artifact_to_representation(representation, artifact): + s.post(representation + "/artifacts", json=[artifact]) + +def add_contract_to_resource(resource, contract): + s.post(resource + "/contracts", json=[contract]) + +def add_rule_to_contract(contract, rule): + s.post(contract + "/rules", json=[rule]) + +# for i in tqdm.tqdm(range(500)): +catalog = create_catalog() +offers = create_offered_resource() +representation = create_representation() +artifact = create_artifact() +contract = create_contract() +use_rule = create_rule() + +add_resource_to_catalog(catalog, offers) +add_representation_to_resource(offers, representation) +add_artifact_to_representation(representation, artifact) +add_contract_to_resource(offers, contract) +add_rule_to_contract(contract, use_rule) + +#################################################################################################### +# CONSUMER (running on port 8081) # +#################################################################################################### + +provider = "https://localhost:8080/api/ids/data" + +def descriptionRequest(recipient, elementId): + params = {} + if recipient is not None: + params['recipient'] = recipient + if elementId is not None: + params['elementId'] = elementId + + return s.post("https://localhost:8081/api/ids/description", params=params) + +def contractRequest(recipient, resourceId, artifactId, download, contract): + params = {} + if recipient is not None: + params['recipient'] = recipient + if resourceId is not None: + params['resourceIds'] = resourceId + if artifactId is not None: + params['artifactIds'] = artifactId + if download is not None: + params['download'] = download + + return s.post("https://localhost:8081/api/ids/contract", params=params, json=[contract]) + +response = descriptionRequest(provider, catalog) +catalogResponse = json.loads(response.text) + +obj = catalogResponse['ids:offeredResource'][0] +resourceId = obj['@id'] +pprint.pprint(resourceId) +contract = obj['ids:contractOffer'][0] +contractId = contract['@id'] +pprint.pprint(contractId) +representation = obj['ids:representation'][0] +artifact = representation['ids:instance'][0] +artifactId = artifact['@id'] +pprint.pprint(artifactId) + +response = descriptionRequest(provider, contractId) +contractResponse = json.loads(response.text) + +obj = contractResponse['ids:permission'][0] +obj['ids:target'] = artifactId + +response = contractRequest(provider, resourceId, artifactId, True, obj) +pprint.pprint(str(response.content)) diff --git a/scripts/tests/remote_data_is_unique.py b/scripts/tests/remote_data_is_unique.py new file mode 100644 index 000000000..36f51896b --- /dev/null +++ b/scripts/tests/remote_data_is_unique.py @@ -0,0 +1,189 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import requests +import pprint +import json +from requests.models import InvalidURL +import tqdm + +# Suppress ssl verification warning +requests.packages.urllib3.disable_warnings() + +s = requests.Session() +s.auth = ('admin', 'password') +s.verify = False + +def create_catalog(): + response = s.post("https://localhost:8080/api/catalogs", json={}) + return response.headers['Location'] + + +def create_offered_resource(): + response = s.post("https://localhost:8080/api/offers", json={}) + return response.headers['Location'] + + +def create_representation(): + response = s.post("https://localhost:8080/api/representations", json={}) + return response.headers['Location'] + + +def create_artifact(): + response = s.post("https://localhost:8080/api/artifacts", json={"value": "SOME LONG VALUE"}) + return response.headers['Location'] + + +def create_contract(): + response = s.post("https://localhost:8080/api/contracts", json={'start': '2021-04-06T13:33:44.995+02:00', 'end':'2021-12-06T13:33:44.995+02:00'}) + return response.headers['Location'] + + +def create_rule_allow_access(): + response = s.post("https://localhost:8080/api/rules", json={'value': """{ + "@type": "ids:Permission", + "@id": "https://w3id.org/idsa/autogen/permission/cf1cb758-b96d-4486-b0a7-f3ac0e289588", + "ids:action": [ + { + "@id": "idsc:USE" + } + ], + "ids:description": [ + { + "@value": "provide-access", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ], + "ids:title": [ + { + "@value": "Example Usage Policy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + } + ] + }"""}) + return response.headers['Location'] + + +def add_resource_to_catalog(catalog, resource): + response = s.post(catalog + "/offers", json=[resource]) + +def add_catalog_to_resource(resource, catalog): + response = s.post(resource + "/catalogs", json=[catalog]) + + +def add_representation_to_resource(resource, representation): + response = s.post(resource + "/representations", + json=[representation]) + + +def add_artifact_to_representation(representation, artifact): + response = s.post(representation + "/artifacts", + json=[artifact]) + + +def add_contract_to_resource(resource, contract): + response = s.post(resource + "/contracts", json=[contract]) + +def add_rule_to_contract(contract, rule): + response = s.post(contract + "/rules", json=[rule]) + + +# IDS +def descriptionRequest(recipient, elementId): + url = "https://localhost:8080/api/ids/description" + params = {} + if recipient is not None: + params['recipient'] = recipient + if elementId is not None: + params['elementId'] = elementId + + return s.post(url, params=params) + + +def contractRequest(recipient, resourceId, artifactId, download, contract): + url = "https://localhost:8080/api/ids/contract" + params = {} + if recipient is not None: + params['recipient'] = recipient + if resourceId is not None: + params['resourceIds'] = resourceId + if artifactId is not None: + params['artifactIds'] = artifactId + if download is not None: + params['download'] = download + + return s.post(url, params=params, json=[contract]) + +# Create resources +catalog = create_catalog() +offers = create_offered_resource() +anotherOffers = create_offered_resource() +representation = create_representation() +artifact = create_artifact() +contract = create_contract() +use_rule = create_rule_allow_access() + +# Link resources +add_resource_to_catalog(catalog, offers) +add_representation_to_resource(offers, representation) +add_representation_to_resource(anotherOffers, representation) +add_artifact_to_representation(representation, artifact) +add_contract_to_resource(offers, contract) +add_contract_to_resource(anotherOffers, contract) +add_rule_to_contract(contract, use_rule) + +# Call description +response = descriptionRequest("https://localhost:8080/api/ids/data", offers) +offer = json.loads(response.text) + +# Negotiate contract +obj = offer['ids:contractOffer'][0]['ids:permission'][0] +obj['ids:target'] = artifact +response = contractRequest("https://localhost:8080/api/ids/data", offers, artifact, False, obj) +pprint.pprint(str(response.content)) + +# Collect stats +numReqResources = json.loads(s.get("https://localhost:8080/api/requests").text)['page']['totalElements'] +numRepresentations = json.loads(s.get("https://localhost:8080/api/representations").text)['page']['totalElements'] +numArtifacts = json.loads(s.get("https://localhost:8080/api/artifacts").text)['page']['totalElements'] +numAgreements = json.loads(s.get("https://localhost:8080/api/agreements").text)['page']['totalElements'] + +# Negotiate over resource whose representations and artifacts are exactly the same +response = descriptionRequest("https://localhost:8080/api/ids/data", anotherOffers) +offer = json.loads(response.text) + +obj = offer['ids:contractOffer'][0]['ids:permission'][0] +obj['ids:target'] = artifact +response = contractRequest("https://localhost:8080/api/ids/data", offers, artifact, False, obj) +pprint.pprint(str(response.content)) + +# Make sure only 2 resources exists all the rest is the same +numReqResourcesAfter = json.loads(s.get("https://localhost:8080/api/requests").text)['page']['totalElements'] +numRepresentationsAfter = json.loads(s.get("https://localhost:8080/api/representations").text)['page']['totalElements'] +numArtifactsAfter = json.loads(s.get("https://localhost:8080/api/artifacts").text)['page']['totalElements'] +numAgreementsAfter = json.loads(s.get("https://localhost:8080/api/agreements").text)['page']['totalElements'] + +if(numReqResources + 1 != numReqResourcesAfter): + raise Exception("Wrong number of requested resources.") + +if(numRepresentations != numRepresentationsAfter): + raise Exception("Wrong number of representations") + +if(numArtifacts != numArtifactsAfter): + raise Exception("Wrong number of artifacts") + +if(numAgreements + 2 != numAgreementsAfter): # +1 as consumer + 1 as provider + raise Exception("Wrong number of agreements") diff --git a/service.yaml b/service.yaml new file mode 100644 index 000000000..ffd1c4574 --- /dev/null +++ b/service.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Service +metadata: + labels: + app: dataspace-connector + name: dataspace-connector +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: dataspace-connector diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java deleted file mode 100644 index e7c58eef9..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Contact; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.info.License; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; - -/** - * This is the main application class. The application is started and an openApi bean for the Swagger UI is created. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@SpringBootApplication -@ComponentScan({ - "de.fraunhofer.isst.ids.framework.messaging.spring.controller", - "de.fraunhofer.isst.ids.framework.messaging.spring", - "de.fraunhofer.isst.dataspaceconnector", -// "de.fraunhofer.isst.ids.framework.configurationmanager.controller", - "de.fraunhofer.isst.ids.framework.spring.starter" -}) -public class ConnectorApplication { - /** - *

main.

- * - * @param args an array of {@link java.lang.String} objects. - */ - public static void main(String[] args) { - SpringApplication.run(ConnectorApplication.class, args); - } - - /** - * Creates the OpenAPI main description. - * - * @return The OpenAPI. - */ - @Bean - public OpenAPI customOpenAPI() { - return new OpenAPI() - .components(new Components()) - .info(new Info() - .title("Dataspace Connector API") - .description("This is the Dataspace Connector's backend API using springdoc-openapi and OpenAPI 3.") - .version("v3.2.1") - .contact(new Contact() - .name("Julia Pampus") - .email("julia.pampus@isst.fraunhofer.de") - ) - .license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0.txt")) - ); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java deleted file mode 100644 index a0bd6b2a1..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; - -/** - * This class configures admin rights for all backend endpoints behind "/admin" using the role defined in {@link de.fraunhofer.isst.dataspaceconnector.config.MultipleEntryPointsSecurityConfig}. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Configuration -public class ConfigurationAdapter extends WebSecurityConfigurerAdapter { - /** {@inheritDoc} */ - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .csrf().disable().formLogin().disable() - .antMatcher("/admin/**") - .authorizeRequests().anyRequest().hasRole("ADMIN") - .and() - .httpBasic() - .authenticationEntryPoint(authenticationEntryPoint()); - http.headers().frameOptions().disable(); - } - - /** - *

authenticationEntryPoint.

- * - * @return a {@link org.springframework.security.web.AuthenticationEntryPoint} object. - */ - @Bean - public AuthenticationEntryPoint authenticationEntryPoint() { - BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); - entryPoint.setRealmName("admin realm"); - return entryPoint; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java deleted file mode 100644 index cf1d1914b..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java +++ /dev/null @@ -1,205 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.spring.starter.BrokerService; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import de.fraunhofer.isst.ids.framework.util.ClientProvider; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.UUID; - -/** - * This class provides endpoints for the communication with an IDS broker instance. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api/broker") -@Tag(name = "Connector: IDS Broker Communication", description = "Endpoints for invoking broker communication") -public class BrokerController { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(BrokerController.class); - - private TokenProvider tokenProvider; - private BrokerService brokerService; - private OfferedResourceService offeredResourceService; - - @Autowired - /** - *

Constructor for BrokerController.

- * - * @param tokenProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param configProducer a {@link de.fraunhofer.isst.ids.framework.spring.starter.ConfigProducer} object. - * @param keyStoreManager a {@link de.fraunhofer.isst.ids.framework.util.KeyStoreManager} object. - */ - public BrokerController(TokenProvider tokenProvider, ConfigurationContainer configurationContainer, OfferedResourceService offeredResourceService) { - this.tokenProvider = tokenProvider; - this.offeredResourceService = offeredResourceService; - try { - this.brokerService = new BrokerService(configurationContainer, new ClientProvider(configurationContainer), - tokenProvider); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - LOGGER.error("(Framework) Broker Service Error: " + e.getMessage()); - } - } - - /** - * Sends a ConnectorAvailableMessage to an IDS broker. - * - * @param url The broker address. - * @return The broker response message or an error. - */ - @Operation(summary = "Register Connector", description = "Register or update connector at an IDS broker.") - @RequestMapping(value = {"/register", "/update"}, method = RequestMethod.POST) - @ResponseBody - public ResponseEntity updateAtBroker(@Parameter(description = "The url of the broker.", required = true, - example = "https://broker.ids.isst.fraunhofer.de/infrastructure") @RequestParam("broker") String url) { - if (tokenProvider.getTokenJWS() != null) { - try { - return new ResponseEntity<>(brokerService.updateAtBroker(url).body().string(), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Broker communication failed: " + e.getMessage()); - return new ResponseEntity<>("Broker communication failed.", HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } - - /** - * Sends a ConnectorUnavailableMessage to an IDS broker. - * - * @param url The broker address. - * @return The broker response message or an error. - */ - @Operation(summary = "Unregister Connector", description = "Unregister connector at an IDS broker.") - @RequestMapping(value = "/unregister", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity unregisterAtBroker(@Parameter(description = "The url of the broker.", required = true, - example = "https://broker.ids.isst.fraunhofer.de/infrastructure") @RequestParam("broker") String url) { - if (tokenProvider.getTokenJWS() != null) { - try { - return new ResponseEntity<>(brokerService.unregisterAtBroker(url).body().string(), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Broker communication failed: " + e.getMessage()); - return new ResponseEntity<>("Broker communication failed.", HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } - - /** - * Sends a QueryMessage to an IDS broker. - * - * @param url The broker address. - * @return The broker response message or an error. - */ - @Operation(summary = "Broker Query Request", description = "Send a query request to an IDS broker.") - @RequestMapping(value = "/query", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity queryBroker(@Parameter(description = "The url of the broker.", required = true, - example = "https://broker.ids.isst.fraunhofer.de/infrastructure") @RequestParam("broker") String url) { - if (tokenProvider.getTokenJWS() != null) { - String query = "SELECT ?subject ?predicate ?object\n" + - "FROM \n" + - "WHERE {\n" + - " ?subject ?predicate ?object\n" + - "}"; - - try { - return new ResponseEntity<>(brokerService.queryBroker(url, query, null, null, null).body().string(), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Broker communication failed: " + e.getMessage()); - return new ResponseEntity<>("Broker communication failed.", HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } - - /** - * Sends a ResourceUpdateMessage to an IDS broker. - * - * @param url The broker address. - * @param resourceId The resource uuid. - * @return The broker response message or an error. - */ - @Operation(summary = "Broker Query Request", description = "Send a query request to an IDS broker.") - @RequestMapping(value = "/resource/{resource-id}/update" , method = RequestMethod.POST) - @ResponseBody - public ResponseEntity updateResourceAtBroker(@Parameter(description = "The url of the broker.", required = true, - example = "https://broker.ids.isst.fraunhofer.de/infrastructure") @RequestParam("broker") String url, - @Parameter(description = "The resource id.", required = true) @PathVariable("resource-id") UUID resourceId) { - if (tokenProvider.getTokenJWS() != null) { - Resource resource; - try { - resource = offeredResourceService.getOfferedResources().get(resourceId); - } catch (Exception e) { - LOGGER.error("Resource could not be found: {}", e.getMessage()); - return new ResponseEntity<>("Resource not found.", HttpStatus.INTERNAL_SERVER_ERROR); - } - - try { - return new ResponseEntity<>(brokerService.updateResourceAtBroker(url, resource).body().string(), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Broker communication failed: " + e.getMessage()); - return new ResponseEntity<>("Broker communication failed.", HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } - - /** - * Sends a ResourceUpdateMessage to an IDS broker. - * - * @param url The broker address. - * @param resourceId The resource uuid. - * @return The broker response message or an error. - */ - @Operation(summary = "Broker Query Request", description = "Send a query request to an IDS broker.") - @RequestMapping(value = "/update/{resource-id}/remove", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity deleteResourceAtBroker(@Parameter(description = "The url of the broker.", required = true, - example = "https://broker.ids.isst.fraunhofer.de/infrastructure") @RequestParam("broker") String url, - @Parameter(description = "The resource id.", required = true) @PathVariable("resource-id") UUID resourceId) { - if (tokenProvider.getTokenJWS() != null) { - Resource resource; - try { - resource = offeredResourceService.getOfferedResources().get(resourceId); - } catch (Exception e) { - LOGGER.error("Resource could not be found: {}", e.getMessage()); - return new ResponseEntity<>("Resource not found.", HttpStatus.INTERNAL_SERVER_ERROR); - } - - try { - return new ResponseEntity<>(brokerService.removeResourceFromBroker(url, resource).body().string(), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Broker communication failed: " + e.getMessage()); - return new ResponseEntity<>("Broker communication failed.", HttpStatus.INTERNAL_SERVER_ERROR); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java deleted file mode 100644 index e7aa2b968..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.iais.eis.ConfigurationModel; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; - -/** - * This class provides endpoints for connector configurations via a connected config manager. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api") -@Tag(name = "Connector Configuration", description = "Endpoints for connector configuration") -public class ConfigurationController { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationController.class); - - private ConfigurationContainer configurationContainer; - private SerializerProvider serializerProvider; - - @Autowired - public ConfigurationController(ConfigurationContainer configurationContainer, SerializerProvider serializerProvider) { - this.configurationContainer = configurationContainer; - this.serializerProvider = serializerProvider; - } - - @Hidden - @RequestMapping(value = "/configuration", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity updateConfiguration(@RequestBody String updatedConfiguration) { - try { - ConfigurationModel configurationModel = serializerProvider.getSerializer().deserialize(updatedConfiguration, ConfigurationModel.class); - configurationContainer.updateConfiguration(configurationModel); - return new ResponseEntity<>("Configuration successfully updated.", HttpStatus.OK); - } catch (ConfigurationUpdateException | IOException e) { - LOGGER.error("Configuration could not be updated: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - @Hidden - @RequestMapping(value = "/configuration", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getConfiguration() { - try { - return new ResponseEntity<>(configurationContainer.getConfigModel().toRdf(), HttpStatus.OK); - } catch (Exception e) { - LOGGER.error("Configuration could not be loaded: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java deleted file mode 100644 index 1bf611592..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java +++ /dev/null @@ -1,290 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.RdfResource; -import de.fraunhofer.iais.eis.util.TypedLiteral; -import de.fraunhofer.iais.eis.util.Util; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; - -/** - * This class provides endpoints for basic connector services. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api") -@Tag(name = "Connector: Selfservice", description = "Endpoints for connector information") -public class MainController { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(MainController.class); - - private TokenProvider tokenProvider; - private ConfigurationContainer configurationContainer; - private SerializerProvider serializerProvider; - - private OfferedResourceService offeredResourceService; - private RequestedResourceService requestedResourceService; - - private PolicyHandler policyHandler; - - @Autowired - /** - *

Constructor for MainController.

- * - * @param policyHandler a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} object. - * @param tokenProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param configProducer a {@link de.fraunhofer.isst.ids.framework.spring.starter.ConfigProducer} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - * @param offeredResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService} object. - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - */ - public MainController(TokenProvider tokenProvider, ConfigurationContainer configurationContainer, - SerializerProvider serializerProvider, OfferedResourceService offeredResourceService, - RequestedResourceService requestedResourceService, PolicyHandler policyHandler) { - this.tokenProvider = tokenProvider; - this.configurationContainer = configurationContainer; - this.serializerProvider = serializerProvider; - this.offeredResourceService = offeredResourceService; - this.requestedResourceService = requestedResourceService; - this.policyHandler = policyHandler; - } - - /** - * Gets connector self-description. - * - * @return Self-description or error response. - */ - @Operation(summary = "Connector Self-description", description = "Get the connector's self-description.") - @RequestMapping(value = {"/selfservice"}, method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getSelfService() { - try { - BaseConnectorImpl connector = (BaseConnectorImpl) configurationContainer.getConnector(); - connector.setResourceCatalog(Util.asList(new ResourceCatalogBuilder() - ._offeredResource_(offeredResourceService.getResourceList()) - ._requestedResource_(requestedResourceService.getRequestedResources()) - .build())); - return new ResponseEntity<>(serializerProvider.getSerializer().serialize(connector), HttpStatus.OK); - } catch (IOException e) { - LOGGER.error("Error during creation of the self description: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - /** - *

getConnector.

- * - * @return a {@link org.springframework.http.ResponseEntity} object. - */ - @Operation(summary = "Get Connector configuration", description = "Get the connector's configuration.") - @RequestMapping(value = "/example/configuration", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getConnector() { - ArrayList exceptions = new ArrayList<>(); - exceptions.add(URI.create("https://localhost:8080/")); - exceptions.add(URI.create("http://localhost:8080/")); - - return new ResponseEntity<>(new ConfigurationModelBuilder() - ._configurationModelLogLevel_(LogLevel.NO_LOGGING) - ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) - ._connectorProxy_(Util.asList(new ProxyBuilder() - ._noProxy_(exceptions) - ._proxyAuthentication_(new BasicAuthenticationBuilder().build()) - ._proxyURI_(URI.create("proxy.dortmund.isst.fraunhofer.de:3128")) - .build())) - ._connectorStatus_(ConnectorStatus.CONNECTOR_ONLINE) - ._connectorDescription_(new BaseConnectorBuilder() - ._maintainer_(URI.create("https://example.com")) - ._curator_(URI.create("https://example.com")) - ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) - ._outboundModelVersion_("4.0.0") - ._inboundModelVersion_(Util.asList("4.0.0")) - ._title_(Util.asList(new TypedLiteral("Dataspace Connector"))) - ._description_(Util.asList(new TypedLiteral("IDS Connector with static example resources hosted by the Fraunhofer ISST"))) - ._version_("v3.0.0") - ._publicKey_(new PublicKeyBuilder() - ._keyType_(KeyType.RSA) //tokenProvider.providePublicKey().getAlgorithm() ? - ._keyValue_(tokenProvider.providePublicKey().getEncoded()) - .build() - ) - ._hasDefaultEndpoint_(new ConnectorEndpointBuilder() - ._accessURL_(URI.create("/api/ids/data")) - .build()) - .build()) - ._keyStore_(URI.create("file:///conf/keystore.p12")) - ._trustStore_(URI.create("file:///conf/truststore.p12")) - .build().toRdf(), HttpStatus.OK); - } - - /** - *

getPolicyPattern.

- * - * @param policy a {@link java.lang.String} object. - * @return a {@link org.springframework.http.ResponseEntity} object. - * @throws java.io.IOException if any. - */ - @Operation(summary = "Get pattern of policy", description = "Get the policy pattern represented by a given JSON string.") - @RequestMapping(value = "/example/policy-pattern", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity getPolicyPattern(@Parameter(description = "The JSON string representing a policy", required = true) - @RequestParam("policy") String policy) throws IOException { - return new ResponseEntity<>(policyHandler.getPattern(policy), HttpStatus.OK); - } - - /** - *

getExampleUsagePolicy.

- * - * @param pattern a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler.Pattern} object. - * @return a {@link org.springframework.http.ResponseEntity} object. - */ - @Operation(summary = "Get example policy", description = "Get an example policy for a given policy pattern.") - @RequestMapping(value = "/example/usage-policy", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity getExampleUsagePolicy(@Parameter(description = "The policy pattern.", required = true) - @RequestParam("pattern") PolicyHandler.Pattern pattern) { - ContractOffer contractOffer = null; - - switch (pattern) { - case PROVIDE_ACCESS: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("provide-access"))) - ._action_(Util.asList(Action.USE)) - .build())) - .build(); - break; - case PROHIBIT_ACCESS: - contractOffer = new ContractOfferBuilder() - ._prohibition_(Util.asList(new ProhibitionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("prohibit-access"))) - ._action_(Util.asList(Action.USE)) - .build())) - .build(); - break; - case N_TIMES_USAGE: - contractOffer = new NotMoreThanNOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("n-times-usage"))) - ._action_(Util.asList(Action.USE)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.COUNT) - ._operator_(BinaryOperator.LTEQ) - ._rightOperand_(new RdfResource("5", URI.create("xsd:double"))) - ._pipEndpoint_(URI.create("https://localhost:8080/admin/api/resources/")) - .build())) - .build())) - .build(); - break; - case DURATION_USAGE: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("duration-usage"))) - ._action_(Util.asList(Action.USE)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.ELAPSED_TIME) - ._operator_(BinaryOperator.SHORTER_EQ) - ._rightOperand_(new RdfResource("PT4H", URI.create("xsd:duration"))) - .build())) - .build())) - .build(); - break; - case USAGE_DURING_INTERVAL: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("usage-during-interval"))) - ._action_(Util.asList(Action.USE)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) - ._operator_(BinaryOperator.AFTER) - ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", URI.create("xsd:dateTimeStamp"))) - .build(), new ConstraintBuilder() - ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) - ._operator_(BinaryOperator.BEFORE) - ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", URI.create("xsd:dateTimeStamp"))) - .build())) - .build())) - .build(); - break; - case USAGE_UNTIL_DELETION: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("usage-until-deletion"))) - ._action_(Util.asList(Action.USE)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) - ._operator_(BinaryOperator.AFTER) - ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", URI.create("xsd:dateTimeStamp"))) - .build(), new ConstraintBuilder() - ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) - ._operator_(BinaryOperator.BEFORE) - ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", URI.create("xsd:dateTimeStamp"))) - .build())) - ._postDuty_(Util.asList(new DutyBuilder() - ._action_(Util.asList(Action.DELETE)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) - ._operator_(BinaryOperator.TEMPORAL_EQUALS) - ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", URI.create("xsd:dateTimeStamp"))) - .build())) - .build())) - .build())) - .build(); - break; - case USAGE_LOGGING: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("usage-logging"))) - ._action_(Util.asList(Action.USE)) - ._postDuty_(Util.asList(new DutyBuilder() - ._action_(Util.asList(Action.LOG)) - .build())) - .build())) - .build(); - break; - case USAGE_NOTIFICATION: - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("usage-notification"))) - ._action_(Util.asList(Action.USE)) - ._postDuty_(Util.asList(new DutyBuilder() - ._action_(Util.asList(Action.NOTIFY)) - ._constraint_(Util.asList(new ConstraintBuilder() - ._leftOperand_(LeftOperand.ENDPOINT) - ._operator_(BinaryOperator.DEFINES_AS) - ._rightOperand_(new RdfResource("https://localhost:8000/api/ids/data", URI.create("xsd:anyURI"))) - .build())) - .build())) - .build())) - .build(); - break; - } - return new ResponseEntity<>(contractOffer.toRdf(), HttpStatus.OK); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java deleted file mode 100644 index f9db450a4..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceImpl; -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceUtils; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import okhttp3.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.net.URI; -import java.util.UUID; - -/** - * This class provides endpoints for the communication with an IDS connector instance. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api/request") -@Tag(name = "Connector: IDS Connector Communication", description = "Endpoints for invoking external connector requests") -public class RequestController { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(RequestController.class); - - private TokenProvider tokenProvider; - private ConnectorRequestServiceImpl requestMessageService; - private ConnectorRequestServiceUtils connectorRequestServiceUtils; - - @Autowired - /** - *

Constructor for RequestController.

- * - * @param tokenProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param requestMessageService a {@link de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceImpl} object. - * @param connectorRequestServiceUtils a {@link de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceUtils} object. - */ - public RequestController(TokenProvider tokenProvider, ConnectorRequestServiceImpl requestMessageService, ConnectorRequestServiceUtils connectorRequestServiceUtils) { - this.tokenProvider = tokenProvider; - this.requestMessageService = requestMessageService; - this.connectorRequestServiceUtils = connectorRequestServiceUtils; - } - - /** - * Actively requests data from an external connector by building an ArtifactRequestMessage. - * - * @param recipient The target connector uri. - * @param requestedArtifact The requested resource uri. - * @return OK or error response. - * @param key a {@link java.util.UUID} object. - * @throws java.io.IOException if any. - */ - @Operation(summary = "Artifact Request", description = "Request data from another IDS connector. " + - "INFO: Before an artifact can be requested, the metadata must be queried. The key generated in this " + - "process must be passed in the artifact query.") - @RequestMapping(value = "/artifact", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity requestData( - @Parameter(description = "The URI of the requested IDS connector.", required = true, - example = "https://localhost:8080/api/ids/data") @RequestParam("recipient") URI recipient, - @Parameter(description = "The URI of the requested artifact.", required = true, - example = "https://w3id.org/idsa/autogen/artifact/a4212311-86e4-40b3-ace3-ef29cd687cf9") - @RequestParam(value = "requestedArtifact") URI requestedArtifact, - @Parameter(description = "A unique validation key.", required = true) @RequestParam("key") UUID key) throws IOException { - if (tokenProvider.getTokenJWS() != null) { - if (connectorRequestServiceUtils.resourceExists(key)) { - Response response = requestMessageService.sendArtifactRequestMessage(recipient, requestedArtifact); - String responseAsString = response.body().string(); - - try { - connectorRequestServiceUtils.saveData(responseAsString, key); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); - } - return new ResponseEntity<>("Saved at: " + key + "\n" - + String.format("Success: %s", (response != null)) + "\n" - + String.format("Body: %s", responseAsString), HttpStatus.OK); - } else { - LOGGER.error("Key is not valid."); - return new ResponseEntity<>("Your key is not valid. Please request metadata first.", HttpStatus.FORBIDDEN); - } - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } - - /** - * Actively requests metadata from an external connector by building an ArtifactRequestMessage. - * - * @param recipient The target connector uri. - * @param requestedArtifact The requested resource uri. - * @return OK or error response. - * @throws java.io.IOException if any. - */ - @Operation(summary = "Description Request", description = "Request metadata from another IDS connector.") - @RequestMapping(value = "/description", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity requestMetadata( - @Parameter(description = "The URI of the requested IDS connector.", required = true, - example = "https://localhost:8080/api/ids/data") @RequestParam("recipient") URI recipient, - @Parameter(description = "The URI of the requested resource.", required = false, - example = "https://w3id.org/idsa/autogen/resource/a4212311-86e4-40b3-ace3-ef29cd687cf9") - @RequestParam(value = "requestedArtifact", required = false) URI requestedArtifact) throws IOException { - if (tokenProvider.getTokenJWS() != null) { - Response response = requestMessageService.sendDescriptionRequestMessage(recipient, requestedArtifact); - String responseAsString = response.body().string(); - - String hint = ""; - if (requestedArtifact != null) { - try { - hint = "Validation key: " + connectorRequestServiceUtils.saveMetadata(responseAsString) + "\n"; - } catch (Exception e) { - LOGGER.error(e.getMessage()); - return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); - } - } - return new ResponseEntity<>(hint - + String.format("Success: %s", (response != null)) + "\n" - + String.format("Body: %s", responseAsString), HttpStatus.OK); - } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java deleted file mode 100644 index 9b3102328..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java +++ /dev/null @@ -1,322 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.util.UUID; - -/** - * This class provides endpoints for the internal resource handling. Resources can be created and modified with it's - * {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} including {@link de.fraunhofer.iais.eis.Contract} - * and {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation}. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api/resources") -@Tag(name = "Connector: Resource Handling", description = "Endpoints for resource handling") -public class ResourceController { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ResourceController.class); - - private OfferedResourceService offeredResourceService; - private RequestedResourceService requestedResourceService; - - private PolicyHandler policyHandler; - - @Autowired - /** - *

Constructor for ResourceController.

- * - * @param offeredResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService} object. - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - * @param policyHandler a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} object. - */ - public ResourceController(OfferedResourceService offeredResourceService, - PolicyHandler policyHandler, RequestedResourceService requestedResourceService) { - this.offeredResourceService = offeredResourceService; - this.requestedResourceService = requestedResourceService; - this.policyHandler = policyHandler; - } - - /** - * Registers a resource with its metadata and, if wanted, with an already existing id. - * - * @param resourceMetadata The resource metadata. - * @param uuid The resource uuid. - * @return The added uuid. - */ - @Operation(summary = "Register Resource", description = "Register a resource by its metadata.") - @RequestMapping(value = "/resource", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity createResource(@RequestBody ResourceMetadata resourceMetadata, @RequestParam(value = "id", required = false) UUID uuid) { - try { - if (uuid != null) { - offeredResourceService.addResourceWithId(resourceMetadata, uuid); - return new ResponseEntity<>("Resource registered with uuid: " + uuid, HttpStatus.CREATED); - } else { - return new ResponseEntity<>("Resource registered with uuid: " + offeredResourceService.addResource(resourceMetadata), HttpStatus.CREATED); - } - } catch (Exception e) { - LOGGER.error("Resource could not be registered: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - /** - * Updates resource metadata by id. - * - * @param id The resource id. - * @param resourceMetadata The updated metadata. - * @return OK or error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Update Resource", description = "Update the resource's metadata by its uuid.") - @RequestMapping(value = "/{resource-id}", method = RequestMethod.PUT) - @ResponseBody - public ResponseEntity updateResource(@Parameter(description = "The resource uuid.", required = true) - @PathVariable("resource-id") UUID id, @RequestBody ResourceMetadata resourceMetadata) throws IllegalArgumentException { - try { - offeredResourceService.updateResource(id, resourceMetadata); - return new ResponseEntity<>("Resource was updated successfully", HttpStatus.OK); - } catch (Exception e) { - LOGGER.error("Resource could not be updated: {}", e.getMessage()); - return new ResponseEntity<>("Resource could not be updated: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - /** - * Gets resource metadata by id. - * - * @param id The resource id. - * @return Matadata or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Get Resource", description = "Get the resource's metadata by its uuid.") - @RequestMapping(value = "/{resource-id}", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getResource( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID id) throws IllegalArgumentException { - try { - return new ResponseEntity<>(offeredResourceService.getMetadata(id), HttpStatus.OK); - } catch (Exception e) { - try { - return new ResponseEntity<>(requestedResourceService.getMetadata(id), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - } - - /** - * Deletes resource by id. - * - * @param id The resource id. - * @return OK or error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Delete Resource", description = "Delete a resource by its uuid.") - @RequestMapping(value = "/{resource-id}", method = RequestMethod.DELETE) - @ResponseBody - public ResponseEntity deleteResource( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID id) throws IllegalArgumentException { - try { - offeredResourceService.deleteResource(id); - return new ResponseEntity<>("Resource was deleted successfully", HttpStatus.OK); - } catch (Exception e) { - try { - requestedResourceService.deleteResource(id); - return new ResponseEntity<>("Resource was deleted successfully", HttpStatus.OK); - } catch (Exception f) { - LOGGER.error("Resource could not be deleted: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - } - - /** - * Updates usage policy. - * - * @param resourceId The resource id. - * @return OK or an error response. - * @param policy The resource's usage policy as string. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Update Resource Contract", description = "Update the resource's usage policy.") - @RequestMapping(value = "/{resource-id}/contract", method = RequestMethod.PUT) - @ResponseBody - public ResponseEntity updateContract( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "A new resource contract.", required = true) @RequestBody String policy) throws IllegalArgumentException { - try { - policyHandler.getPattern(policy); - } catch (IOException e) { - return new ResponseEntity<>("Policy syntax error: " + e.getMessage(), HttpStatus.BAD_REQUEST); - } - - try { - offeredResourceService.updateContract(resourceId, policy); - return new ResponseEntity<>("Contract was updated successfully", HttpStatus.OK); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - - /** - * Gets usage policy. - * - * @param resourceId The resource id. - * @return Contract or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Get Resource Contract", description = "Get the resource's usage policy.") - @RequestMapping(value = "/{resource-id}/contract", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getContract(@Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId) throws IllegalArgumentException { - try { - return new ResponseEntity<>(offeredResourceService.getMetadata(resourceId).getPolicy(), HttpStatus.OK); - } catch (Exception e) { - try { - return new ResponseEntity<>(requestedResourceService.getMetadata(resourceId).getPolicy(), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - } - - /** - *

getAccess.

- * - * @param resourceId a {@link java.util.UUID} object. - * @return a {@link org.springframework.http.ResponseEntity} object. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Get Data Access", description = "Get the number of the resource's data access.") - @RequestMapping(value = "/{resource-id}/access", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getAccess(@Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId) throws IllegalArgumentException { - try { - return new ResponseEntity<>(requestedResourceService.getResource(resourceId).getAccessed(), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - - /** - * Adds resource representation. - * - * @param resourceId The resource id. - * @param representation A new representation. - * @return OK or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Add Representation", description = "Add a representation to a resource.") - @RequestMapping(value = "/{resource-id}/representation", method = RequestMethod.POST) - @ResponseBody - public ResponseEntity addRepresentation( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "A new resource representation.", required = true) @RequestBody ResourceRepresentation representation, - @RequestParam(value = "id", required = false) UUID uuid) throws IllegalArgumentException { - try { - if(uuid != null){ - offeredResourceService.addRepresentationWithId(resourceId, representation, uuid); - }else { - uuid = offeredResourceService.addRepresentation(resourceId, representation); - } - return new ResponseEntity<>("Representation was saved successfully with uuid: " + uuid, HttpStatus.CREATED); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - - /** - * Updates resource representation. - * - * @param resourceId The resource id. - * @param representationId The representation id. - * @param representation A new representation. - * @return OK or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Update representation", description = "Update a resource's representation by its uuid.") - @RequestMapping(value = "/{resource-id}/{representation-id}", method = RequestMethod.PUT) - @ResponseBody - public ResponseEntity updateRepresentation( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "The representation uuid.", required = true) @PathVariable("representation-id") UUID representationId, - @Parameter(description = "A new resource representation.", required = true) @RequestBody ResourceRepresentation representation) throws IllegalArgumentException { - try { - offeredResourceService.updateRepresentation(resourceId, representationId, representation); - return new ResponseEntity<>("Representation was updated successfully", HttpStatus.OK); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - - /** - * Adds resource representation. - * - * @param resourceId The resource id. - * @param representationId The representation id. - * @return OK or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Get Resource Representation", description = "Get the resource's representation by its uuid.") - @RequestMapping(value = "/{resource-id}/{representation-id}", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getRepresentation( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "The representation uuid.", required = true) @PathVariable("representation-id") UUID representationId) throws IllegalArgumentException { - try { - return new ResponseEntity<>(offeredResourceService.getRepresentation(resourceId, representationId), HttpStatus.OK); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - - /** - * Removes resource representation. - * - * @param resourceId The resource id. - * @param representationId The representation id. - * @return OK or an error response. - * @throws java.lang.IllegalArgumentException if any. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Remove Resource Representation", description = "Remove a resource's representation by its uuid.") - @RequestMapping(value = "/{resource-id}/{representation-id}", method = RequestMethod.DELETE) - @ResponseBody - public ResponseEntity deleteRepresentation( - @Parameter(description = "The resource uuid.", required = true) @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "The representation uuid.", required = true) @PathVariable("representation-id") UUID representationId) throws IllegalArgumentException { - try { - offeredResourceService.deleteRepresentation(resourceId, representationId); - return new ResponseEntity<>("Representation was deleted successfully", HttpStatus.OK); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java deleted file mode 100644 index 1ff1225ea..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java +++ /dev/null @@ -1,123 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.controller; - -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.UUID; - -/** - * This class provides endpoints for the internal resource handling. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@RestController -@RequestMapping("/admin/api/resources") -@Tag(name = "Backend: Resource Data Handling", description = "Endpoints for resource data handling") -public class ResourceDataController { // Header: Content-Type: application/json - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ResourceDataController.class); - - private OfferedResourceService offeredResourceService; - private RequestedResourceService requestedResourceService; - - @Autowired - /** - *

Constructor for ResourceDataController.

- * - * @param offeredResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService} object. - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - */ - public ResourceDataController(OfferedResourceService offeredResourceService, RequestedResourceService requestedResourceService) { - this.offeredResourceService = offeredResourceService; - this.requestedResourceService = requestedResourceService; - } - - /** - * Publishes the resource's data as a string. - * - * @param id The resource id. - * @param data The data string. - * @return Ok or error response. - * @throws java.lang.IllegalArgumentException If the requested id is not a uuid. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Publish Resource Data String", description = "Publish resource data as string.") - @RequestMapping(value = "/{resource-id}/data", method = {RequestMethod.PUT}) // , RequestMethod.POST - // params = { "type=string" } NOT SUPPORTED with OpenAPI - @ResponseBody - public ResponseEntity publishResource( - @Parameter(description = "The resource uuid.", required = true, example = "a4212311-86e4-40b3-ace3-ef29cd687cf9") @PathVariable("resource-id") UUID id, - @Parameter(description = "The resource data.", required = true, example = "Data String") @RequestParam("data") String data) - throws IllegalArgumentException { - try { - offeredResourceService.addData(id, data); - return new ResponseEntity<>("Resource published", HttpStatus.CREATED); - } catch (Exception e) { - LOGGER.error("Resource could not be published: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - /** - * Gets resource data as a string. - * - * @param id The resource id. - * @return Raw data or an error response. - * @throws java.lang.IllegalArgumentException If the requested id is not a uuid. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Request Data String", description = "Get the resource's data as a string.") - @RequestMapping(value = "/{resource-id}/data", method = RequestMethod.GET) - // params = {"type=string"} NOT SUPPORTED with OpenAPI - @ResponseBody - public ResponseEntity getDataById( - @Parameter(description = "The resource uuid.", required = true, example = "a4212311-86e4-40b3-ace3-ef29cd687cf9") @PathVariable("resource-id") UUID id) - throws IllegalArgumentException { - try { - return new ResponseEntity<>(offeredResourceService.getData(id), HttpStatus.OK); - } catch (Exception e) { - try { - return new ResponseEntity<>(requestedResourceService.getData(id), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - } - - /** - * Gets resource data as a string by representation id. - * - * @param resourceId The resource id. - * @param representationId The representation id. - * @return Raw data or an error response. - * @throws java.lang.IllegalArgumentException If the requested id is not a uuid. - * @throws java.lang.IllegalArgumentException if any. - */ - @Operation(summary = "Request Data String by Representation", description = "Get the resource's data as a string by representation.") - @RequestMapping(value = "/{resource-id}/{representation-id}/data", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity getDataByRepresentation( - @Parameter(description = "The resource uuid.", required = true, example = "a4212311-86e4-40b3-ace3-ef29cd687cf9") @PathVariable("resource-id") UUID resourceId, - @Parameter(description = "The representation uuid.", required = true) @PathVariable("representation-id") UUID representationId) - throws IllegalArgumentException { - try { - return new ResponseEntity<>(offeredResourceService.getDataByRepresentation(resourceId, representationId), HttpStatus.OK); - } catch (Exception e) { - try { - return new ResponseEntity<>(requestedResourceService.getData(resourceId), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); - } - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/ArtifactMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/ArtifactMessageHandler.java deleted file mode 100644 index b9daea10e..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/ArtifactMessageHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.message; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.Util; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.MessageHandler; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.SupportedMessageType; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.BodyResponse; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.ErrorResponse; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessagePayload; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessageResponse; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.net.URI; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This @{@link de.fraunhofer.isst.dataspaceconnector.message.ArtifactMessageHandler} handles all incoming messages that - * have a {@link de.fraunhofer.iais.eis.ArtifactRequestMessageImpl} as part one in the multipart message. This header - * must have the correct '@type' reference as defined in the {@link de.fraunhofer.iais.eis.ArtifactRequestMessageImpl} - * JsonTypeName annotation. In this example, the received payload is not defined and will be returned immediately. - * Usually, the payload would be well defined as well, such that it can be deserialized into a proper Java-Object. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -@SupportedMessageType(ArtifactRequestMessageImpl.class) -public class ArtifactMessageHandler implements MessageHandler { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ArtifactMessageHandler.class); - - private final TokenProvider provider; - private final Connector connector; - - private OfferedResourceService offeredResourceService; - private PolicyHandler policyHandler; - - @Autowired - /** - *

Constructor for ArtifactMessageHandler.

- * - * @param offeredResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService} object. - * @param provider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param connector a {@link de.fraunhofer.iais.eis.Connector} object. - * @param policyHandler a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} object. - */ - public ArtifactMessageHandler(OfferedResourceService offeredResourceService, TokenProvider provider, - ConfigurationContainer configurationContainer, PolicyHandler policyHandler) { - this.offeredResourceService = offeredResourceService; - this.provider = provider; - this.connector = configurationContainer.getConnector(); - this.policyHandler = policyHandler; - } - - /** - * {@inheritDoc} - * - * This message implements the logic that is needed to handle the message. As it just returns the input as string - * the messagePayload-InputStream is converted to a String. - */ - @Override - public MessageResponse handleMessage(ArtifactRequestMessageImpl requestMessage, MessagePayload messagePayload) { - ResponseMessage responseMessage = new ArtifactResponseMessageBuilder() - ._securityToken_(provider.getTokenJWS()) - ._correlationMessage_(requestMessage.getId()) - ._issued_(de.fraunhofer.isst.ids.framework.messaging.core.handler.api.util.Util.getGregorianNow()) - ._issuerConnector_(connector.getId()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._senderAgent_(connector.getId()) - ._recipientConnector_(Util.asList(requestMessage.getIssuerConnector())) - .build(); - - UUID artifactId = uuidFromUri(requestMessage.getRequestedArtifact()); - URI requestedResource = null; - - for (Resource resource : offeredResourceService.getResourceList()) { - for (Representation representation : resource.getRepresentation()) { - UUID representationId = uuidFromUri(representation.getId()); - - if (representationId.equals(artifactId)) { - requestedResource = resource.getId(); - } - } - } - - try { - UUID resourceId = uuidFromUri(requestedResource); - ResourceMetadata resourceMetadata = offeredResourceService.getMetadata(resourceId); - try { - if (policyHandler.onDataProvision(resourceMetadata.getPolicy())) { - String data = offeredResourceService.getDataByRepresentation(resourceId, artifactId); - return BodyResponse.create(responseMessage, data); - } else { - LOGGER.error("Policy restriction detected: " + policyHandler.getPattern(resourceMetadata.getPolicy())); - return ErrorResponse.withDefaultHeader(RejectionReason.NOT_AUTHORIZED, "Policy restriction detected: You are not authorized to receive this data.", connector.getId(), connector.getOutboundModelVersion()); - } - } catch (Exception e) { - LOGGER.error("Exception: {}", e.getMessage()); - return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, e.getMessage(), connector.getId(), connector.getOutboundModelVersion()); - } - } catch (Exception e) { - LOGGER.error("Resource could not be found."); - return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, "An artifact with the given uuid is not known to the connector: {}" + e.getMessage(), connector.getId(), connector.getOutboundModelVersion()); - } - } - - /** - * Extracts the uuid from a uri. - * - * @param uri The base uri. - * @return Uuid as String. - */ - private UUID uuidFromUri(URI uri) { - Pattern pairRegex = Pattern.compile("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}"); - Matcher matcher = pairRegex.matcher(uri.toString()); - String uuid = ""; - while (matcher.find()) { - uuid = matcher.group(0); - } - return UUID.fromString(uuid); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/DescriptionMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/DescriptionMessageHandler.java deleted file mode 100644 index e4178bc29..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/DescriptionMessageHandler.java +++ /dev/null @@ -1,131 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.message; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.Util; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.MessageHandler; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.SupportedMessageType; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.BodyResponse; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.ErrorResponse; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessagePayload; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessageResponse; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.net.URI; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This @{@link de.fraunhofer.isst.dataspaceconnector.message.DescriptionMessageHandler} handles all incoming messages - * that have a {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} as part one in the multipart message. This - * header must have the correct '@type' reference as defined in the {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} - * JsonTypeName annotation. In this example, the received payload is not defined and will be returned immediately. - * Usually, the payload would be well defined as well, such that it can be deserialized into a proper Java-Object. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -@SupportedMessageType(DescriptionRequestMessageImpl.class) -public class DescriptionMessageHandler implements MessageHandler { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(DescriptionMessageHandler.class); - - private OfferedResourceService offeredResourceService; - private RequestedResourceService requestedResourceService; - private TokenProvider provider; - private Connector connector; - - private ConfigurationContainer configurationContainer; - private SerializerProvider serializerProvider; - - @Autowired - /** - *

Constructor for DescriptionMessageHandler.

- * - * @param offeredResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService} object. - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - * @param provider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param connector a {@link de.fraunhofer.iais.eis.Connector} object. - * @param configProducer a {@link de.fraunhofer.isst.ids.framework.spring.starter.ConfigProducer} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - */ - public DescriptionMessageHandler(OfferedResourceService offeredResourceService, RequestedResourceService requestedResourceService, - TokenProvider provider, ConfigurationContainer configurationContainer, - SerializerProvider serializerProvider) { - this.offeredResourceService = offeredResourceService; - this.requestedResourceService = requestedResourceService; - this.provider = provider; - this.connector = configurationContainer.getConnector(); - this.configurationContainer = configurationContainer; - this.serializerProvider = serializerProvider; - } - - /** - * {@inheritDoc} - * - * This message implements the logic that is needed to handle the message. As it just returns the input as string - * the messagePayload-InputStream is converted to a String. - */ - @Override - public MessageResponse handleMessage(DescriptionRequestMessageImpl requestMessage, MessagePayload messagePayload) { - ResponseMessage responseMessage = new DescriptionResponseMessageBuilder() - ._securityToken_(provider.getTokenJWS()) - ._correlationMessage_(requestMessage.getId()) - ._issued_(de.fraunhofer.isst.ids.framework.messaging.core.handler.api.util.Util.getGregorianNow()) - ._issuerConnector_(connector.getId()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._senderAgent_(connector.getId()) - ._recipientConnector_(Util.asList(requestMessage.getIssuerConnector())) - .build(); - - if (requestMessage.getRequestedElement() != null) { - UUID resourceId = uuidFromUri(requestMessage.getRequestedElement()); - - try { - Resource resource = offeredResourceService.getOfferedResources().get(resourceId); - return BodyResponse.create(responseMessage, resource.toRdf()); - } catch (Exception e) { - LOGGER.error("Resource could not be found: {}", String.valueOf(e.getMessage())); - return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, String.valueOf(e.getMessage()), connector.getId(), connector.getOutboundModelVersion()); - } - } else { - try { - BaseConnectorImpl connector = (BaseConnectorImpl) configurationContainer.getConnector(); - connector.setResourceCatalog(Util.asList(new ResourceCatalogBuilder() - ._offeredResource_(offeredResourceService.getResourceList()) - ._requestedResource_(requestedResourceService.getRequestedResources()) - .build())); - return BodyResponse.create(responseMessage, serializerProvider.getSerializer().serialize(connector)); - } catch (IOException e) { - LOGGER.error("Self description could not be created: {}", e.getMessage()); - return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, "Self description could not be created: {}" + e.getMessage(), connector.getId(), connector.getOutboundModelVersion()); - } - } - } - - /** - * Extracts the uuid from a uri. - * - * @param uri The base uri. - * @return Uuid as String. - */ - private UUID uuidFromUri(URI uri) { - Pattern pairRegex = Pattern.compile("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}"); - Matcher matcher = pairRegex.matcher(uri.toString()); - String uuid = ""; - while (matcher.find()) { - uuid = matcher.group(0); - } - return UUID.fromString(uuid); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/NotificationMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/NotificationMessageHandler.java deleted file mode 100644 index d50ed4981..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/message/NotificationMessageHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.message; - -import de.fraunhofer.iais.eis.DescriptionRequestMessageImpl; -import de.fraunhofer.iais.eis.NotificationMessageImpl; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.MessageHandler; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.SupportedMessageType; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessagePayload; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.model.MessageResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -/** - * This @{@link de.fraunhofer.isst.dataspaceconnector.message.NotificationMessageHandler} handles all incoming messages - * that have a {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} as part one in the multipart message. This - * header must have the correct '@type' reference as defined in the {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} - * JsonTypeName annotation. In this example, the received payload is not defined and will be returned immediately. - * Usually, the payload would be well defined as well, such that it can be deserialized into a proper Java-Object. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -@SupportedMessageType(NotificationMessageImpl.class) -public class NotificationMessageHandler implements MessageHandler { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(NotificationMessageHandler.class); - - /** - * {@inheritDoc} - * - * This message implements the logic that is needed to handle the message. As it just returns the input as string - * the messagePayload-InputStream is converted to a String. - */ - @Override - public MessageResponse handleMessage(NotificationMessageImpl requestMessage, MessagePayload messagePayload) { - LOGGER.info("USAGE LOGGED: " + messagePayload); - return null; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/BackendSource.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/BackendSource.java deleted file mode 100644 index 4da259517..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/BackendSource.java +++ /dev/null @@ -1,125 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.io.Serializable; -import java.net.URI; - -/** - *

BackendSource class.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -@Schema( - name = "BackendSource", - description = "Information of the backend system.", - oneOf = BackendSource.class -) -public class BackendSource implements Serializable { - @JsonProperty("url") - private URI url; - - @JsonProperty("username") - private String username; - - @JsonProperty("password") - private String password; - - @JsonProperty("system") - private String system; - - /** - *

Constructor for BackendSource.

- */ - public BackendSource() { - } - - /** - *

Constructor for BackendSource.

- * - * @param url a {@link java.net.URI} object. - * @param username a {@link java.lang.String} object. - * @param password a {@link java.lang.String} object. - * @param system a {@link java.lang.String} object. - */ - public BackendSource(URI url, String username, String password, String system) { - this.url = url; - this.username = username; - this.password = password; - this.system = system; - } - - /** - *

Getter for the field url.

- * - * @return a {@link java.net.URI} object. - */ - public URI getUrl() { - return url; - } - - /** - *

Setter for the field url.

- * - * @param url a {@link java.net.URI} object. - */ - public void setUrl(URI url) { - this.url = url; - } - - /** - *

Getter for the field username.

- * - * @return a {@link java.lang.String} object. - */ - public String getUsername() { - return username; - } - - /** - *

Setter for the field username.

- * - * @param username a {@link java.lang.String} object. - */ - public void setUsername(String username) { - this.username = username; - } - - /** - *

Getter for the field password.

- * - * @return a {@link java.lang.String} object. - */ - public String getPassword() { - return password; - } - - /** - *

Setter for the field password.

- * - * @param password a {@link java.lang.String} object. - */ - public void setPassword(String password) { - this.password = password; - } - - /** - *

Getter for the field system.

- * - * @return a {@link java.lang.String} object. - */ - public String getSystem() { - return system; - } - - /** - *

Setter for the field system.

- * - * @param system a {@link java.lang.String} object. - */ - public void setSystem(String system) { - this.system = system; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java deleted file mode 100644 index 2664098b1..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java +++ /dev/null @@ -1,82 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import java.util.Date; -import java.util.UUID; - -/** - *

ConnectorResource interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -public interface ConnectorResource { - /** - *

getUuid.

- * - * @return a {@link java.util.UUID} object. - */ - UUID getUuid(); - - /** - *

setUuid.

- * - * @param uuid a {@link java.util.UUID} object. - */ - void setUuid(UUID uuid); - - /** - *

getCreated.

- * - * @return a {@link java.util.Date} object. - */ - Date getCreated(); - - /** - *

setCreated.

- * - * @param created a {@link java.util.Date} object. - */ - void setCreated(Date created); - - /** - *

getModified.

- * - * @return a {@link java.util.Date} object. - */ - Date getModified(); - - /** - *

setModified.

- * - * @param modified a {@link java.util.Date} object. - */ - void setModified(Date modified); - - /** - *

getResourceMetadata.

- * - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - */ - ResourceMetadata getResourceMetadata(); - - /** - *

setResourceMetadata.

- * - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - */ - void setResourceMetadata(ResourceMetadata resourceMetadata); - - /** - *

getData.

- * - * @return a {@link java.lang.String} object. - */ - String getData(); - - /** - *

setData.

- * - * @param data a {@link java.lang.String} object. - */ - void setData(String data); -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/OfferedResource.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/OfferedResource.java deleted file mode 100644 index 4bebe91d9..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/OfferedResource.java +++ /dev/null @@ -1,123 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import javax.persistence.*; -import javax.validation.constraints.NotNull; -import java.util.Date; -import java.util.UUID; - -/** - * This class provides a custom data resource with an id, data and metadata to be saved in a h2 database. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Data -@Entity -@Table -public class OfferedResource implements ConnectorResource{ - @Id - @JsonProperty("uuid") - private UUID uuid; - - @JsonProperty("created") - private Date created; - - @JsonProperty("modified") - private Date modified; - - @NotNull - @Column(columnDefinition = "BYTEA") - @JsonProperty("metadata") - private ResourceMetadata resourceMetadata; - - @Column(columnDefinition = "TEXT") - @JsonProperty("data") - private String data; - - /** - *

Constructor for OfferedResource.

- */ - public OfferedResource() { - - } - - /** - *

Constructor for OfferedResource.

- * - * @param uuid a {@link java.util.Date} object. - * @param created a {@link java.util.Date} object. - * @param modified a {@link java.util.Date} object. - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - * @param data a {@link java.lang.String} object. - */ - public OfferedResource(UUID uuid, Date created, Date modified, ResourceMetadata resourceMetadata, String data) { - this.uuid = uuid; - this.created = created; - this.modified = modified; - this.resourceMetadata = resourceMetadata; - this.data = data; - } - - /** {@inheritDoc} */ - @Override - public UUID getUuid() { - return uuid; - } - - /** {@inheritDoc} */ - @Override - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - /** {@inheritDoc} */ - @Override - public Date getCreated() { - return created; - } - - /** {@inheritDoc} */ - @Override - public void setCreated(Date created) { - this.created = created; - } - - /** {@inheritDoc} */ - @Override - public Date getModified() { - return modified; - } - - /** {@inheritDoc} */ - @Override - public void setModified(Date modified) { - this.modified = modified; - } - - /** {@inheritDoc} */ - @Override - public ResourceMetadata getResourceMetadata() { - return resourceMetadata; - } - - /** {@inheritDoc} */ - @Override - public void setResourceMetadata(ResourceMetadata resourceMetadata) { - this.resourceMetadata = resourceMetadata; - } - - /** {@inheritDoc} */ - @Override - public String getData() { - return data; - } - - /** {@inheritDoc} */ - @Override - public void setData(String data) { - this.data = data; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/RequestedResource.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/RequestedResource.java deleted file mode 100644 index 5cd6a9660..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/RequestedResource.java +++ /dev/null @@ -1,145 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import javax.persistence.*; -import javax.validation.constraints.NotNull; -import java.util.Date; -import java.util.UUID; - -/** - * This class provides a custom data resource with an id, data and metadata to be saved in a h2 database. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Data -@Entity -@Table -public class RequestedResource implements ConnectorResource{ - @Id - @GeneratedValue - @JsonProperty("uuid") - private UUID uuid; - - @JsonProperty("created") - private Date created; - - @JsonProperty("modified") - private Date modified; - - @NotNull - @Column(columnDefinition = "BYTEA") - @JsonProperty("metadata") - private ResourceMetadata resourceMetadata; - - @Column(columnDefinition = "TEXT") - @JsonProperty("data") - private String data; - - @JsonProperty("accessed") - private Integer accessed; - - /** - *

Constructor for RequestedResource.

- */ - public RequestedResource() { - - } - - /** - *

Constructor for RequestedResource.

- * - * @param created a {@link java.util.Date} object. - * @param modified a {@link java.util.Date} object. - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - * @param data a {@link java.lang.String} object. - * @param accessed a {@link java.lang.Integer} object. - */ - public RequestedResource(Date created, Date modified, ResourceMetadata resourceMetadata, String data, Integer accessed) { - this.created = created; - this.modified = modified; - this.resourceMetadata = resourceMetadata; - this.data = data; - this.accessed = accessed; - } - - /** {@inheritDoc} */ - @Override - public UUID getUuid() { - return uuid; - } - - /** {@inheritDoc} */ - @Override - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - /** {@inheritDoc} */ - @Override - public Date getCreated() { - return created; - } - - /** {@inheritDoc} */ - @Override - public void setCreated(Date created) { - this.created = created; - } - - /** {@inheritDoc} */ - @Override - public Date getModified() { - return modified; - } - - /** {@inheritDoc} */ - @Override - public void setModified(Date modified) { - this.modified = modified; - } - - /** {@inheritDoc} */ - @Override - public ResourceMetadata getResourceMetadata() { - return resourceMetadata; - } - - /** {@inheritDoc} */ - @Override - public void setResourceMetadata(ResourceMetadata resourceMetadata) { - this.resourceMetadata = resourceMetadata; - } - - /** {@inheritDoc} */ - @Override - public String getData() { - return data; - } - - /** {@inheritDoc} */ - @Override - public void setData(String data) { - this.data = data; - } - - /** - *

Getter for the field accessed.

- * - * @return a {@link java.lang.Integer} object. - */ - public Integer getAccessed() { - return accessed; - } - - /** - *

Setter for the field accessed.

- * - * @param accessed a {@link java.lang.Integer} object. - */ - public void setAccessed(Integer accessed) { - this.accessed = accessed; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java deleted file mode 100644 index ed65de1f1..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java +++ /dev/null @@ -1,255 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.swagger.v3.oas.annotations.media.Schema; - -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.validation.constraints.NotNull; -import java.io.Serializable; -import java.net.URI; -import java.util.List; - -/** - * This class provides a model to handle data resource metadata. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Schema( - name = "ResourceMetadata", - description = "Metadata of a resource", - oneOf = ResourceMetadata.class, - example = "{\n" + - " \"title\": \"Sample Resource\",\n" + - " \"description\": \"This is an example resource containing weather data.\",\n" + - " \"keywords\": [\n" + - " \"weather\",\n" + - " \"data\",\n" + - " \"sample\"\n" + - " ],\n" + - " \"owner\": \"https://openweathermap.org/\",\n" + - " \"license\": \"http://opendatacommons.org/licenses/odbl/1.0/\",\n" + - " \"version\": \"1.0\"\n" + - "}\n" -) -public class ResourceMetadata implements Serializable { - @JsonProperty("title") - private String title; - - @JsonProperty("description") - private String description; - - @ElementCollection - @JsonProperty("keywords") - private List keywords; - - @Column(columnDefinition = "BYTEA") - @JsonProperty("policy") - private String policy; - - @JsonProperty("owner") - private URI owner; - - @JsonProperty("license") - private URI license; - - @JsonProperty("version") - private String version; - - @NotNull - @ElementCollection - @Column(columnDefinition = "BYTEA") - @JsonProperty("representations") - private List representations; - - /** - *

Constructor for ResourceMetadata.

- */ - public ResourceMetadata() { - - } - - /** - *

Constructor for ResourceMetadata.

- * - * @param title a {@link java.lang.String} object. - * @param description a {@link java.lang.String} object. - * @param keywords a {@link java.util.List} object. - * @param policy a {@link java.lang.String} object. - * @param owner a {@link java.net.URI} object. - * @param license a {@link java.net.URI} object. - * @param version a {@link java.lang.String} object. - * @param representations a {@link java.util.List} object. - */ - public ResourceMetadata(String title, String description, List keywords, String policy, - URI owner, URI license, String version, List representations) { - this.title = title; - this.description = description; - this.keywords = keywords; - this.policy = policy; - this.owner = owner; - this.license = license; - this.version = version; - this.representations = representations; - } - - /** - *

Getter for the field title.

- * - * @return a {@link java.lang.String} object. - */ - public String getTitle() { - return title; - } - - /** - *

Setter for the field title.

- * - * @param title a {@link java.lang.String} object. - */ - public void setTitle(String title) { - this.title = title; - } - - /** - *

Getter for the field description.

- * - * @return a {@link java.lang.String} object. - */ - public String getDescription() { - return description; - } - - /** - *

Setter for the field description.

- * - * @param description a {@link java.lang.String} object. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - *

Getter for the field keywords.

- * - * @return a {@link java.util.List} object. - */ - public List getKeywords() { - return keywords; - } - - /** - *

Setter for the field keywords.

- * - * @param keywords a {@link java.util.List} object. - */ - public void setKeywords(List keywords) { - this.keywords = keywords; - } - - /** - *

Getter for the field policy.

- * - * @return a {@link java.lang.String} object. - */ - public String getPolicy() { - return policy; - } - - /** - *

Setter for the field policy.

- * - * @param policy a {@link java.lang.String} object. - */ - public void setPolicy(String policy) { - this.policy = policy; - } - - /** - *

Getter for the field owner.

- * - * @return a {@link java.net.URI} object. - */ - public URI getOwner() { - return owner; - } - - /** - *

Setter for the field owner.

- * - * @param owner a {@link java.net.URI} object. - */ - public void setOwner(URI owner) { - this.owner = owner; - } - - /** - *

Getter for the field license.

- * - * @return a {@link java.net.URI} object. - */ - public URI getLicense() { - return license; - } - - /** - *

Setter for the field license.

- * - * @param license a {@link java.net.URI} object. - */ - public void setLicense(URI license) { - this.license = license; - } - - /** - *

Getter for the field version.

- * - * @return a {@link java.lang.String} object. - */ - public String getVersion() { - return version; - } - - /** - *

Setter for the field version.

- * - * @param version a {@link java.lang.String} object. - */ - public void setVersion(String version) { - this.version = version; - } - - /** - *

Getter for the field representations.

- * - * @return a {@link java.util.List} object. - */ - public List getRepresentations() { - return representations; - } - - /** - *

Setter for the field representations.

- * - * @param representations a {@link java.util.List} object. - */ - public void setRepresentations(List representations) { - this.representations = representations; - } - - /** {@inheritDoc} */ - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - String jsonString = null; - try { - jsonString = mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - return jsonString; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java deleted file mode 100644 index af2115257..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java +++ /dev/null @@ -1,194 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; - -import javax.persistence.Column; -import javax.persistence.Id; -import java.io.Serializable; -import java.util.UUID; - -/** - *

ResourceRepresentation class.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -@Schema( - name = "ResourceRepresentation", - description = "Representation of a resource", - oneOf = ResourceRepresentation.class, - example = "{\n" + - " \"type\": \"json\",\n" + - " \"byteSize\": 105,\n" + - " \"sourceType\": \"http-get\",\n" + - " \"source\": {\n" + - " \"url\": \"https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02\",\n" + - " \"username\": \"-\",\n" + - " \"password\": \"-\",\n" + - " \"system\": \"Open Weather Map API\"\n" + - " }\n" + - " }" -) -public class ResourceRepresentation implements Serializable { - @Schema( - name = "SourceType", - description = "Information of the backend system.", - oneOf = SourceType.class - ) - public enum SourceType { - @JsonProperty("local") - LOCAL("local"), - @JsonProperty("http-get") - HTTP_GET("http-get"), - @JsonProperty("http-get-basicauth") - HTTP_GET_BASICAUTH("http-get-basicauth"), - @JsonProperty("https-get") - HTTPS_GET("https-get"), - @JsonProperty("https-get-basicauth") - HTTPS_GET_BASICAUTH("https-get-basicauth"), - @JsonProperty("mongodb") - MONGODB("mongodb"); - - private final String type; - - SourceType(String string) { - type = string; - } - - @Override - public String toString() { - return type; - } - } - - @Id - @JsonProperty("uuid") - private UUID uuid; - - @JsonProperty("type") - private String type; - - @JsonProperty("byteSize") - private Integer byteSize; - - @JsonProperty("sourceType") - private SourceType sourceType; - - @JsonProperty("source") - @Column(columnDefinition = "BLOB") - private BackendSource source; - - /** - *

Constructor for ResourceRepresentation.

- */ - public ResourceRepresentation() { - } - - /** - *

Constructor for ResourceRepresentation.

- * - * @param uuid a {@link java.util.UUID} object. - * @param type a {@link java.lang.String} object. - * @param byteSize a {@link java.lang.Integer} object. - * @param sourceType a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation.SourceType} object. - * @param source a {@link de.fraunhofer.isst.dataspaceconnector.model.BackendSource} object. - */ - public ResourceRepresentation(UUID uuid, String type, Integer byteSize, SourceType sourceType, BackendSource source) { - this.uuid = uuid; - this.type = type; - this.byteSize = byteSize; - this.sourceType = sourceType; - this.source = source; - } - - /** - *

Getter for the field uuid.

- * - * @return a {@link java.util.UUID} object. - */ - public UUID getUuid() { - return uuid; - } - - /** - *

Setter for the field uuid.

- * - * @param uuid a {@link java.util.UUID} object. - */ - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - /** - *

Getter for the field type.

- * - * @return a {@link java.lang.String} object. - */ - public String getType() { - return type; - } - - /** - *

Setter for the field type.

- * - * @param type a {@link java.lang.String} object. - */ - public void setType(String type) { - this.type = type; - } - - /** - *

Getter for the field byteSize.

- * - * @return a {@link java.lang.Integer} object. - */ - public Integer getByteSize() { - return byteSize; - } - - /** - *

Setter for the field byteSize.

- * - * @param byteSize a {@link java.lang.Integer} object. - */ - public void setByteSize(Integer byteSize) { - this.byteSize = byteSize; - } - - /** - *

Getter for the field sourceType.

- * - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation.SourceType} object. - */ - public SourceType getSourceType() { - return sourceType; - } - - /** - *

Setter for the field sourceType.

- * - * @param sourceType a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation.SourceType} object. - */ - public void setSourceType(SourceType sourceType) { - this.sourceType = sourceType; - } - - /** - *

Getter for the field source.

- * - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.BackendSource} object. - */ - public BackendSource getSource() { - return source; - } - - /** - *

Setter for the field source.

- * - * @param source a {@link de.fraunhofer.isst.dataspaceconnector.model.BackendSource} object. - */ - public void setSource(BackendSource source) { - this.source = source; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/HttpUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/HttpUtils.java deleted file mode 100644 index 071b2f067..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/HttpUtils.java +++ /dev/null @@ -1,175 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services; - -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.util.ClientProvider; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import org.apache.commons.codec.binary.Base64; -import org.apache.http.HttpHeaders; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -/** - * This class builds up http or https endpoint connections. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class HttpUtils { - - private ClientProvider clientProvider; - - @Autowired - /** - *

Constructor for HttpUtils.

- * - * @param configurationModel a {@link de.fraunhofer.iais.eis.ConfigurationModel} object. - * @param keyStoreManager a {@link de.fraunhofer.isst.ids.framework.util.KeyStoreManager} object. - */ - public HttpUtils(ConfigurationContainer configurationContainer) throws NoSuchAlgorithmException, KeyManagementException { - this.clientProvider = new ClientProvider(configurationContainer); - } - - /** - * Sends a get request to an external http endpoint. - * - * @param address The url. - * @return The http response. - * @throws java.io.IOException if any. - */ - public String sendHttpGetRequest(String address) throws IOException { - URL url = new URL(address); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - - int status = con.getResponseCode(); - if (status == 200) { - BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuilder content = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - con.disconnect(); - - return content.toString(); - } else { - return null; - } - } - - /** - * Sends a post request to an external http endpoint. - * - * @param endpoint The requested url. - * @return Response as string. - * @param input an array of {@link byte} objects. - * @throws java.io.IOException if any. - */ - public int sendHttpPostRequest(String endpoint, byte[] input) throws IOException { - URL url = new URL(endpoint); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setRequestProperty("Content-Type", "application/json; utf-8"); - con.setDoOutput(true); - - try (OutputStream os = con.getOutputStream()) { - os.write(input, 0, input.length); - } - - int responseCode = con.getResponseCode(); - con.disconnect(); - - return responseCode; - } - - /** - *

sendHttpGetRequestWithBasicAuth.

- * - * @param address a {@link java.lang.String} object. - * @param username a {@link java.lang.String} object. - * @param password a {@link java.lang.String} object. - * @return a {@link java.lang.String} object. - */ - public String sendHttpGetRequestWithBasicAuth(String address, String username, String password) { - return null; - } - - /** - *

sendHttpsGetRequest.

- * - * @param address a {@link java.lang.String} object. - * @return a {@link java.lang.String} object. - * @throws java.io.IOException if any. - * @throws java.security.KeyManagementException if any. - * @throws java.security.NoSuchAlgorithmException if any. - */ - public String sendHttpsGetRequest(String address) throws IOException, KeyManagementException, NoSuchAlgorithmException { - Request request = new Request.Builder() - .url(address) - .get() - .build(); - - OkHttpClient client = clientProvider.getClient(); - Response response = client.newCall(request).execute(); - - if (response.code() < 200 || response.code() >= 300) { - response.close(); - throw new IOException("Not OK"); - } else { - String rawResponseString = new String(response.body().byteStream().readAllBytes()); - response.close(); - - return rawResponseString; - } - } - - /** - * Sends a get request with basic authentication to an external https endpoint. - * - * @param address The url. - * @param username The username. - * @param password The password. - * @return The http response. - * @throws java.io.IOException if any. - * @throws java.security.KeyManagementException if any. - * @throws java.security.NoSuchAlgorithmException if any. - */ - public String sendHttpsGetRequestWithBasicAuth(String address, String username, String password) throws IOException, KeyManagementException, NoSuchAlgorithmException { - String auth = username + ":" + password; - byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); - String authHeader = "Basic " + new String(encodedAuth); - - Request request = new Request.Builder() - .url(address) - .header(HttpHeaders.AUTHORIZATION, authHeader) - .get() - .build(); - - OkHttpClient client = clientProvider.getClient(); - Response response = client.newCall(request).execute(); - - if (response.code() < 200 || response.code() >= 300) { - response.close(); - throw new IOException("Not OK"); - } else { - String rawResponseString = new String(response.body().byteStream().readAllBytes()); - response.close(); - - return rawResponseString; - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/IdsUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/IdsUtils.java deleted file mode 100644 index 44db53f09..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/IdsUtils.java +++ /dev/null @@ -1,147 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.TypedLiteral; -import de.fraunhofer.iais.eis.util.Util; -import de.fraunhofer.isst.dataspaceconnector.model.ConnectorResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; -import java.io.IOException; -import java.math.BigInteger; -import java.net.URI; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.GregorianCalendar; - -/** - * This class provides methods to map local connector models to IDS-specific Information Model objects. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class IdsUtils { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(IdsUtils.class); - - private ConfigurationContainer configurationContainer; - private SerializerProvider serializerProvider; - private String language; - - @Autowired - /** - *

Constructor for IdsUtils.

- * - * @param configProducer a {@link de.fraunhofer.isst.ids.framework.spring.starter.ConfigProducer} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - */ - public IdsUtils(ConfigurationContainer configurationContainer, SerializerProvider serializerProvider) { - this.configurationContainer = configurationContainer; - this.serializerProvider = serializerProvider; - - language = configurationContainer.getConnector().getLabel().get(0).getLanguage(); - } - - /** - * Gets the resource metadata as Information Model object. - * - * @param resource The connector resource. - * @return The Information Model resource. - */ - public Resource getAsResource(ConnectorResource resource) { - ResourceMetadata metadata = resource.getResourceMetadata(); - - ArrayList keywords = new ArrayList<>(); - if (metadata.getKeywords() != null) { - for (String keyword : metadata.getKeywords()) { - keywords.add(new TypedLiteral(keyword, language)); - } - } - - ArrayList representations = new ArrayList<>(); - if (metadata.getRepresentations() != null) { - for (ResourceRepresentation representation : metadata.getRepresentations()) { - representations.add(new RepresentationBuilder(URI.create("https://w3id.org/idsa/autogen/representation/" + representation.getUuid())) - ._language_(Language.EN) - ._mediaType_(new IANAMediaTypeBuilder() - ._filenameExtension_(representation.getType()) - .build()) - ._instance_(Util.asList(new ArtifactBuilder(URI.create("https://w3id.org/idsa/autogen/artifact/" + representation.getUuid())) - ._byteSize_(BigInteger.valueOf(representation.getByteSize())) - .build())) - .build()); - } - } - - Contract contract = null; - if (resource.getResourceMetadata().getPolicy() != null) { - try { - contract = serializerProvider.getSerializer().deserialize(resource.getResourceMetadata().getPolicy(), Contract.class); - } catch (IOException e) { - LOGGER.error("Could not deserialize contract: " + e.getMessage()); - } - } - - return new ResourceBuilder(URI.create("https://w3id.org/idsa/autogen/resource/" + resource.getUuid())) - ._contractOffer_(Util.asList((ContractOffer) contract)) - ._created_(getGregorianOf(resource.getCreated())) - ._description_(Util.asList(new TypedLiteral(metadata.getDescription(), language))) - ._keyword_(keywords) - ._language_(Util.asList(Language.EN)) - ._modified_(getGregorianOf(resource.getModified())) - ._publisher_(metadata.getOwner()) - ._representation_(representations) - ._resourceEndpoint_(Util.asList(configurationContainer.getConnector().getHasDefaultEndpoint())) - ._standardLicense_(metadata.getLicense()) - ._title_(Util.asList(new TypedLiteral(metadata.getTitle(), language))) - ._version_(metadata.getVersion()) - .build(); - } - - /** - * Converts a date to XMLGregorianCalendar format. - * - * @param date The date object. - * @return The XMLGregorianCalendar object or null. - */ - private XMLGregorianCalendar getGregorianOf(Date date) { - GregorianCalendar c = new GregorianCalendar(); - c.setTime(date); - try { - return DatatypeFactory.newInstance().newXMLGregorianCalendar(c); - } catch (DatatypeConfigurationException e) { - LOGGER.error(e.getMessage()); - return null; - } - } - - /** - * Converts a string to XMLGregorianCalendar format. - * - * @param string The string. - * @return The XMLGregorianCalendar object or null. - */ - private XMLGregorianCalendar stringToDate(String string) { - DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); - Date date = null; - try { - date = format.parse(string); - } catch (ParseException e) { - e.printStackTrace(); - } - return getGregorianOf(date); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestService.java deleted file mode 100644 index 55ffa7099..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestService.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.communication; - -import okhttp3.Response; - -import java.io.IOException; -import java.net.URI; - -/** - *

ConnectorRequestService interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -public interface ConnectorRequestService { - /** - *

sendArtifactRequestMessage.

- * - * @param recipient a {@link java.net.URI} object. - * @param artifact a {@link java.net.URI} object. - * @return a {@link okhttp3.Response} object. - * @throws java.io.IOException if any. - */ - Response sendArtifactRequestMessage(URI recipient, URI artifact) throws IOException; - - /** - *

sendDescriptionRequestMessage.

- * - * @param recipient a {@link java.net.URI} object. - * @param artifact a {@link java.net.URI} object. - * @return a {@link okhttp3.Response} object. - * @throws java.io.IOException if any. - */ - Response sendDescriptionRequestMessage(URI recipient, URI artifact) throws IOException; - - /** - *

sendContractRequestMessage.

- * - * @return a {@link okhttp3.Response} object. - */ - Response sendContractRequestMessage(); -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceImpl.java deleted file mode 100644 index b8cc7d129..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.communication; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.exceptions.HttpClientException; -import de.fraunhofer.isst.ids.framework.messages.InfomodelMessageBuilder; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.util.Util; -import de.fraunhofer.isst.ids.framework.spring.starter.IDSHttpService; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import de.fraunhofer.isst.ids.framework.util.ClientProvider; -import okhttp3.MultipartBody; -import okhttp3.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -/** - * This class implements all methods of {@link de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestService}. It provides message handling for all outgoing - * connector communication by passing IDS messages to the IDS framework. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class ConnectorRequestServiceImpl implements ConnectorRequestService { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ConnectorRequestServiceImpl.class); - - private Connector connector; - private TokenProvider tokenProvider; - private IDSHttpService idsHttpService; - - @Autowired - /** - *

Constructor for ConnectorRequestServiceImpl.

- * - * @param configurationContainer a {@link de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer} object. - * @param tokenProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @throws de.fraunhofer.isst.ids.framework.exceptions.HttpClientException if any. - * @throws java.security.KeyManagementException if any. - * @throws java.security.NoSuchAlgorithmException if any. - */ - public ConnectorRequestServiceImpl(ConfigurationContainer configurationContainer, TokenProvider tokenProvider) - throws HttpClientException, KeyManagementException, NoSuchAlgorithmException { - this.connector = configurationContainer.getConnector(); - this.tokenProvider = tokenProvider; - - ClientProvider clientProvider = new ClientProvider(configurationContainer); - this.idsHttpService = new IDSHttpService(clientProvider); - } - - /** - * {@inheritDoc} - * - * Builds and sends an ArtifactRequestMessage. - */ - @Override - public Response sendArtifactRequestMessage(URI recipient, URI artifact) throws IOException { - ArtifactRequestMessage requestMessage = new ArtifactRequestMessageBuilder() - ._issued_(Util.getGregorianNow()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._issuerConnector_(connector.getId()) - ._senderAgent_(connector.getId()) - ._requestedArtifact_(artifact) - ._securityToken_(tokenProvider.getTokenJWS()) - ._recipientConnector_(de.fraunhofer.iais.eis.util.Util.asList(recipient)) - .build(); - - MultipartBody body = InfomodelMessageBuilder.messageWithString(requestMessage, ""); - return idsHttpService.send(body, recipient); - } - - /** - * {@inheritDoc} - * - * Builds and sends an DescriptionRequestMessage. - */ - @Override - public Response sendDescriptionRequestMessage(URI recipient, URI artifact) throws IOException { - DescriptionRequestMessage requestMessage; - - if (artifact == null) { - requestMessage = new DescriptionRequestMessageBuilder() - ._issued_(Util.getGregorianNow()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._issuerConnector_(connector.getId()) - ._senderAgent_(connector.getId()) - ._securityToken_(tokenProvider.getTokenJWS()) - ._recipientConnector_(de.fraunhofer.iais.eis.util.Util.asList(recipient)) - .build(); - } else { - requestMessage = new DescriptionRequestMessageBuilder() - ._issued_(Util.getGregorianNow()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._issuerConnector_(connector.getId()) - ._senderAgent_(connector.getId()) - ._requestedElement_(artifact) - ._securityToken_(tokenProvider.getTokenJWS()) - ._recipientConnector_(de.fraunhofer.iais.eis.util.Util.asList(recipient)) - .build(); - } - - MultipartBody body = InfomodelMessageBuilder.messageWithString(requestMessage, ""); - return idsHttpService.send(body, recipient); - } - - /** {@inheritDoc} */ - @Override - public Response sendContractRequestMessage() { - return null; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceUtils.java deleted file mode 100644 index f1ed7e3b7..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/ConnectorRequestServiceUtils.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.communication; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.TypedLiteral; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import de.fraunhofer.isst.ids.framework.util.MultipartStringParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * This class handles received message content and saves the metadata and data to the internal database. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class ConnectorRequestServiceUtils { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(ConnectorRequestServiceUtils.class); - - private RequestedResourceService requestedResourceService; - private SerializerProvider serializerProvider; - - @Autowired - /** - *

Constructor for ConnectorRequestServiceUtils.

- * - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - */ - public ConnectorRequestServiceUtils(RequestedResourceService requestedResourceService, SerializerProvider serializerProvider) { - this.requestedResourceService = requestedResourceService; - this.serializerProvider = serializerProvider; - } - - /** - * Saves the metadata in the internal database. - * - * @param response The data resource as string. - * @return The UUID of the created resource. - * @throws java.lang.Exception if any. - */ - public UUID saveMetadata(String response) throws Exception { - Map map = MultipartStringParser.stringToMultipart(response); - String header = map.get("header"); - String payload = map.get("payload"); - - try { - serializerProvider.getSerializer().deserialize(header, DescriptionResponseMessage.class); -// ObjectMapper mapper = new ObjectMapper(); -// ResourceMetadata resourceMetadata = mapper.readValue(payload, ResourceMetadata.class); - - Resource resource = serializerProvider.getSerializer().deserialize(payload, ResourceImpl.class); - return requestedResourceService.addResource(deserializeMetadata(resource)); - } catch (Exception e) { - throw new Exception("Metadata could not be saved: " + e.getMessage()); - } - } - - /** - * Saves the data string in the internal database. - * - * @param response The data resource as string. - * @param resourceId The resource uuid. - * @throws java.lang.Exception if any. - */ - public void saveData(String response, UUID resourceId) throws Exception { - Map map = MultipartStringParser.stringToMultipart(response); - String header = map.get("header"); - String payload = map.get("payload"); - - try { - serializerProvider.getSerializer().deserialize(header, ArtifactResponseMessage.class); - } catch (Exception e) { - throw new Exception("Rejection Message received: " + payload); - } - - try { - requestedResourceService.addData(resourceId, payload); - } catch (Exception e) { - throw new Exception("Data could not be saved: " + e.getMessage()); - } - } - - /** - *

resourceExists.

- * - * @param resourceId a {@link java.util.UUID} object. - * @return a boolean. - */ - public boolean resourceExists(UUID resourceId) { - return requestedResourceService.getResource(resourceId) != null; - } - - private ResourceMetadata deserializeMetadata(Resource resource) { - List keywords = new ArrayList<>(); - for(TypedLiteral t : resource.getKeyword()) { - keywords.add(t.getValue()); - } - - List representations = new ArrayList<>(); - for(Representation r : resource.getRepresentation()) { - Artifact artifact = (Artifact) r.getInstance().get(0); - ResourceRepresentation representation = new ResourceRepresentation( - UUID.randomUUID(), - r.getMediaType().getFilenameExtension(), - artifact.getByteSize().intValue(), - ResourceRepresentation.SourceType.LOCAL, - null - ); - representations.add(representation); - } - - return new ResourceMetadata( - resource.getTitle().get(0).getValue(), - resource.getDescription().get(0).getValue(), - keywords, - resource.getContractOffer().get(0).toRdf(), - resource.getPublisher(), - resource.getStandardLicense(), - resource.getVersion(), - representations - ); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageService.java deleted file mode 100644 index 273c249e8..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageService.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.communication; - -import de.fraunhofer.isst.ids.framework.exceptions.HttpClientException; -import okhttp3.Response; - -import java.io.IOException; - -/** - *

MessageService interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -public interface MessageService { - /** - *

sendLogMessage.

- * - * @return a {@link okhttp3.Response} object. - * @throws de.fraunhofer.isst.ids.framework.exceptions.HttpClientException if any. - * @throws java.io.IOException if any. - */ - Response sendLogMessage() throws HttpClientException, IOException; - - /** - *

sendNotificationMessage.

- * - * @param recipient a {@link java.lang.String} object. - * @return a {@link okhttp3.Response} object. - * @throws de.fraunhofer.isst.ids.framework.exceptions.HttpClientException if any. - * @throws java.io.IOException if any. - */ - Response sendNotificationMessage(String recipient) throws HttpClientException, IOException; -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageServiceImpl.java deleted file mode 100644 index 8a8ca4cbd..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/communication/MessageServiceImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.communication; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; -import de.fraunhofer.isst.ids.framework.messages.InfomodelMessageBuilder; -import de.fraunhofer.isst.ids.framework.messaging.core.handler.api.util.Util; -import de.fraunhofer.isst.ids.framework.spring.starter.IDSHttpService; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import okhttp3.MultipartBody; -import okhttp3.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.net.URI; - -/** - *

MessageServiceImpl class.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class MessageServiceImpl implements MessageService { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class); - - private Connector connector; - private TokenProvider tokenProvider; - private IDSHttpService idsHttpService; - - @Autowired - /** - *

Constructor for MessageServiceImpl.

- * - * @param connector a {@link de.fraunhofer.iais.eis.Connector} object. - * @param tokenProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider} object. - * @param idsHttpService a {@link de.fraunhofer.isst.ids.framework.spring.starter.IDSHttpService} object. - */ - public MessageServiceImpl(ConfigurationContainer configurationContainer, TokenProvider tokenProvider, - IDSHttpService idsHttpService) { - this.connector = configurationContainer.getConnector(); - this.tokenProvider = tokenProvider; - this.idsHttpService = idsHttpService; - } - - /** {@inheritDoc} */ - @Override - public Response sendLogMessage() throws IOException { - LogMessage message = new LogMessageBuilder() - ._issued_(Util.getGregorianNow()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._issuerConnector_(connector.getId()) - ._senderAgent_(connector.getId()) - ._securityToken_(tokenProvider.getTokenJWS()) - .build(); - - MultipartBody body = InfomodelMessageBuilder.messageWithString(message, ""); - LOGGER.info("TO IMPLEMENT | NOT SENT: " + body); -// return idsHttpService.send(body, URI.create(recipient)); TODO send log messages - return null; - } - - /** {@inheritDoc} */ - @Override - public Response sendNotificationMessage(String recipient) throws IOException { - NotificationMessage message = new NotificationMessageBuilder() - ._issued_(Util.getGregorianNow()) - ._modelVersion_(connector.getOutboundModelVersion()) - ._issuerConnector_(connector.getId()) - ._senderAgent_(connector.getId()) - ._securityToken_(tokenProvider.getTokenJWS()) - ._recipientConnector_(de.fraunhofer.iais.eis.util.Util.asList(URI.create(recipient))) - .build(); - - MultipartBody body = InfomodelMessageBuilder.messageWithString(message, ""); - LOGGER.info("TO IMPLEMENT | NOT SENT: " + body); -// return idsHttpService.send(body, URI.create(recipient)); TODO send notification messages - return null; - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java deleted file mode 100644 index ef3b57abc..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.isst.dataspaceconnector.model.OfferedResource; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.UUID; - -/** - *

OfferedResourceRepository interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -@Repository -public interface OfferedResourceRepository extends JpaRepository { -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceService.java deleted file mode 100644 index a1d387e8a..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceService.java +++ /dev/null @@ -1,159 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.isst.dataspaceconnector.model.OfferedResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; - -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; - -/** - *

OfferedResourceService interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -public interface OfferedResourceService { - /** - *

getResourceList.

- * - * @return a {@link java.util.ArrayList} object. - */ - ArrayList getResourceList(); - - /** - *

getOfferedResources.

- * - * @return a {@link java.util.Map} object. - */ - Map getOfferedResources(); - - /** - *

addResource.

- * - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - * @return a {@link java.util.UUID} object. - */ - UUID addResource(ResourceMetadata resourceMetadata); - - /** - *

addResourceWithId.

- * - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - * @param uuid a {@link java.util.UUID} object. - */ - void addResourceWithId(ResourceMetadata resourceMetadata, UUID uuid); - - /** - *

addData.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param data a {@link java.lang.String} object. - */ - void addData(UUID resourceId, String data); - - /** - *

updateResource.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - */ - void updateResource(UUID resourceId, ResourceMetadata resourceMetadata); - - /** - *

updateContract.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param policy a {@link java.lang.String} object. - */ - void updateContract(UUID resourceId, String policy); - - /** - *

deleteResource.

- * - * @param resourceId a {@link java.util.UUID} object. - */ - void deleteResource(UUID resourceId); - - /** - *

getResource.

- * - * @param resourceId a {@link java.util.UUID} object. - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.OfferedResource} object. - */ - OfferedResource getResource(UUID resourceId); - - /** - *

getMetadata.

- * - * @param resourceId a {@link java.util.UUID} object. - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - */ - ResourceMetadata getMetadata(UUID resourceId); - - /** - *

getData.

- * - * @param resourceId a {@link java.util.UUID} object. - * @return a {@link java.lang.String} object. - * @throws java.lang.Exception if any. - */ - String getData(UUID resourceId) throws Exception; - - /** - *

getDataByRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representationId a {@link java.util.UUID} object. - * @return a {@link java.lang.String} object. - * @throws java.lang.Exception if any. - */ - String getDataByRepresentation(UUID resourceId, UUID representationId) throws Exception; - - /** - *

addRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representation a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation} object. - * @return a {@link java.util.UUID} object. - */ - UUID addRepresentation(UUID resourceId, ResourceRepresentation representation); - - /** - *

addRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representation a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation} object. - * @param representationId the {@link UUID} that will be used for the new representation - * @return a {@link java.util.UUID} object. - */ - UUID addRepresentationWithId(UUID resourceId, ResourceRepresentation representation, UUID representationId); - - /** - *

updateRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representationId a {@link java.util.UUID} object. - * @param representation a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation} object. - */ - void updateRepresentation(UUID resourceId, UUID representationId, ResourceRepresentation representation); - - /** - *

getRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representationId a {@link java.util.UUID} object. - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation} object. - */ - ResourceRepresentation getRepresentation(UUID resourceId, UUID representationId); - - /** - *

deleteRepresentation.

- * - * @param resourceId a {@link java.util.UUID} object. - * @param representationId a {@link java.util.UUID} object. - */ - void deleteRepresentation(UUID resourceId, UUID representationId); -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceServiceImpl.java deleted file mode 100644 index dbea694e0..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceServiceImpl.java +++ /dev/null @@ -1,350 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.util.TypedLiteral; -import de.fraunhofer.iais.eis.util.Util; -import de.fraunhofer.isst.dataspaceconnector.model.OfferedResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; -import de.fraunhofer.isst.dataspaceconnector.services.HttpUtils; -import de.fraunhofer.isst.dataspaceconnector.services.IdsUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.*; - -/** - * This class implements all methods of {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService}. It provides database resource handling for all offered resources. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class OfferedResourceServiceImpl implements OfferedResourceService { - /** - * Constant LOGGER - */ - public static final Logger LOGGER = LoggerFactory.getLogger(OfferedResourceServiceImpl.class); - - private OfferedResourceRepository offeredResourceRepository; - private HttpUtils httpUtils; - private IdsUtils idsUtils; - - private Map offeredResources; - private ContractOffer contractOffer; - - @Autowired - /** - *

Constructor for OfferedResourceServiceImpl.

- * - * @param offeredResourceRepository a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceRepository} object. - * @param httpUtils a {@link de.fraunhofer.isst.dataspaceconnector.services.HttpUtils} object. - * @param idsUtils a {@link de.fraunhofer.isst.dataspaceconnector.services.IdsUtils} object. - */ - public OfferedResourceServiceImpl(OfferedResourceRepository offeredResourceRepository, HttpUtils httpUtils, IdsUtils idsUtils) { - this.offeredResourceRepository = offeredResourceRepository; - this.httpUtils = httpUtils; - this.idsUtils = idsUtils; - - contractOffer = new ContractOfferBuilder() - ._permission_(Util.asList(new PermissionBuilder() - ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) - ._description_(Util.asList(new TypedLiteral("provide-access"))) - ._action_(Util.asList(Action.USE)) - .build())) - .build(); - - offeredResources = new HashMap<>(); - for (OfferedResource resource : offeredResourceRepository.findAll()) { - offeredResources.put(resource.getUuid(), idsUtils.getAsResource(resource)); - } - } - - /** - * {@inheritDoc} - *

- * Returns the resource list. - */ - @Override - public ArrayList getResourceList() { - return new ArrayList<>(offeredResources.values()); - } - - /** {@inheritDoc} */ - @Override - public Map getOfferedResources() { - return offeredResources; - } - - /** - * {@inheritDoc} - *

- * Saves the resources with its metadata as external resource or internal resource. - */ - @Override - public UUID addResource(ResourceMetadata resourceMetadata) { - resourceMetadata.setPolicy(contractOffer.toRdf()); - OfferedResource resource = new OfferedResource(createUuid(), new Date(), new Date(), resourceMetadata, ""); - - offeredResourceRepository.save(resource); - offeredResources.put(resource.getUuid(), idsUtils.getAsResource(resource)); - - return resource.getUuid(); - } - - @Override - public void addResourceWithId(ResourceMetadata resourceMetadata, UUID uuid) { - resourceMetadata.setPolicy(contractOffer.toRdf()); - OfferedResource resource = new OfferedResource(uuid, new Date(), new Date(), resourceMetadata, ""); - - offeredResourceRepository.save(resource); - offeredResources.put(resource.getUuid(), idsUtils.getAsResource(resource)); - } - - /** - * {@inheritDoc} - *

- * Publishes the resource data. - */ - @Override - public void addData(UUID resourceId, String data) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - resource.setData(data); - resource.setModified(new Date()); - - offeredResourceRepository.save(resource); - } - - /** - * {@inheritDoc} - *

- * Updates resource metadata by id. - */ - @Override - public void updateResource(UUID resourceId, ResourceMetadata resourceMetadata) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - resource.setModified(new Date()); - resource.setResourceMetadata(resourceMetadata); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - } - - /** {@inheritDoc} */ - @Override - public void updateContract(UUID resourceId, String policy) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - resource.getResourceMetadata().setPolicy(policy); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - } - - /** - * {@inheritDoc} - *

- * Deletes a resource by id. - */ - @Override - public void deleteResource(UUID resourceId) { - offeredResourceRepository.deleteById(resourceId); - offeredResources.remove(resourceId); - } - - /** - * {@inheritDoc} - *

- * Gets a resource by id. - */ - @Override - public OfferedResource getResource(UUID resourceId) { - return offeredResourceRepository.getOne(resourceId); - } - - /** - * {@inheritDoc} - *

- * Gets resource metadata by id. - */ - @Override - public ResourceMetadata getMetadata(UUID resourceId) { - return offeredResourceRepository.getOne(resourceId).getResourceMetadata(); - } - - /** - * {@inheritDoc} - *

- * Gets data from local database. - */ - @Override - public String getData(UUID resourceId) throws Exception { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - return getDataString(resource, resource.getResourceMetadata().getRepresentations().get(0)); - } - - /** - * {@inheritDoc} - *

- * Gets data from local or external data source. - */ - @Override - public String getDataByRepresentation(UUID resourceId, UUID representationId) throws Exception { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - String data = ""; - for (ResourceRepresentation representation : resource.getResourceMetadata().getRepresentations()) { - if (representation.getUuid().equals(representationId)) { - data = getDataString(resource, representation); - } - } - return data; - } - - /** {@inheritDoc} */ - @Override - public UUID addRepresentation(UUID resourceId, ResourceRepresentation representation) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - resource.setModified(new Date()); - - if (resource.getResourceMetadata().getRepresentations() == null) { - resource.getResourceMetadata().setRepresentations(new ArrayList<>()); - } - representation.setUuid(UUID.randomUUID()); - resource.getResourceMetadata().getRepresentations().add(representation); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - - return representation.getUuid(); - } - - /** {@inheritDoc} */ - @Override - public UUID addRepresentationWithId(UUID resourceId, ResourceRepresentation representation, UUID representationId) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - resource.setModified(new Date()); - - if (resource.getResourceMetadata().getRepresentations() == null) { - resource.getResourceMetadata().setRepresentations(new ArrayList<>()); - } - representation.setUuid(representationId); - resource.getResourceMetadata().getRepresentations().add(representation); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - - return representation.getUuid(); - } - - /** {@inheritDoc} */ - @Override - public void updateRepresentation(UUID resourceId, UUID representationId, ResourceRepresentation representation) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - resource.setModified(new Date()); - - representation.setUuid(representationId); - resource.getResourceMetadata().getRepresentations().set(getIndex(resource, representationId), representation); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - } - - /** {@inheritDoc} */ - @Override - public ResourceRepresentation getRepresentation(UUID resourceId, UUID representationId) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - return resource.getResourceMetadata().getRepresentations().get(getIndex(resource, representationId)); - } - - /** {@inheritDoc} */ - @Override - public void deleteRepresentation(UUID resourceId, UUID representationId) { - OfferedResource resource = offeredResourceRepository.getOne(resourceId); - - resource.getResourceMetadata().getRepresentations().remove(getIndex(resource, representationId)); - - offeredResourceRepository.save(resource); - offeredResources.put(resourceId, idsUtils.getAsResource(resource)); - } - - /** - * Returns the representation index for further operations. - * - * @param resource The resource object. - * @param representationId The representation id. - * @return The index - */ - private int getIndex(OfferedResource resource, UUID representationId) { - int index = -1; - for (ResourceRepresentation r : resource.getResourceMetadata().getRepresentations()) { - if (r.getUuid().toString().equals(representationId.toString())) { - index = resource.getResourceMetadata().getRepresentations().indexOf(r); - } - } - return index; - } - - /** - * Gets data as string. - * - * @param resource The connector resource object. - * @param representation The representation. - * @return The string or an exception. - * @throws Exception If the data could not be retrieved. - */ - private String getDataString(OfferedResource resource, ResourceRepresentation representation) throws Exception { - String address = null; - String username = null; - String password = null; - - if (representation.getSource() != null) { - address = representation.getSource().getUrl().toString(); - username = representation.getSource().getUsername(); - password = representation.getSource().getPassword(); - } - - switch (representation.getSourceType()) { - case LOCAL: - return resource.getData(); - case HTTP_GET: - return httpUtils.sendHttpGetRequest(address); - case HTTP_GET_BASICAUTH: - return httpUtils.sendHttpGetRequestWithBasicAuth(address, username, password); - case HTTPS_GET: - return httpUtils.sendHttpsGetRequest(address); - case HTTPS_GET_BASICAUTH: - return httpUtils.sendHttpsGetRequestWithBasicAuth(address, username, password); - case MONGODB: - // TODO - throw new Exception("Could not retrieve data."); - default: - throw new Exception("Could not retrieve data."); - } - } - - /** - * Generates a unique uuid for a resource, if it does not already exist. - * - * @return Generated uuid - */ - private UUID createUuid() { - UUID uuid = UUID.randomUUID(); - ArrayList list = new ArrayList<>(); - - for (OfferedResource r : offeredResourceRepository.findAll()) { - list.add(r.getUuid()); - } - - if (!list.contains(uuid)) { - return uuid; - } else { - return createUuid(); - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java deleted file mode 100644 index 4ce8335c6..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.UUID; - -/** - *

RequestedResourceRepository interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -@Repository -public interface RequestedResourceRepository extends JpaRepository { -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceService.java deleted file mode 100644 index 8915c5a47..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceService.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.UUID; - -/** - *

RequestedResourceService interface.

- * - * @author Julia Pampus - * @version $Id: $Id - */ -public interface RequestedResourceService { - /** - *

addResource.

- * - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - * @return a {@link java.util.UUID} object. - */ - UUID addResource(ResourceMetadata resourceMetadata); - - /** - *

addData.

- * - * @param id a {@link java.util.UUID} object. - * @param data a {@link java.lang.String} object. - */ - void addData(UUID id, String data); - - /** - *

deleteResource.

- * - * @param id a {@link java.util.UUID} object. - */ - void deleteResource(UUID id); - - /** - *

getResource.

- * - * @param id a {@link java.util.UUID} object. - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.RequestedResource} object. - */ - RequestedResource getResource(UUID id); - - /** - *

getMetadata.

- * - * @param id a {@link java.util.UUID} object. - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. - */ - ResourceMetadata getMetadata(UUID id); - - /** - *

getData.

- * - * @param id a {@link java.util.UUID} object. - * @return a {@link java.lang.String} object. - * @throws java.io.IOException if any. - */ - String getData(UUID id) throws IOException; - - /** - *

getRequestedResources.

- * - * @return a {@link java.util.ArrayList} object. - */ - ArrayList getRequestedResources(); -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceServiceImpl.java deleted file mode 100644 index 7eb632b3c..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceServiceImpl.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; - -import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.services.IdsUtils; -import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.util.*; - -/** - * This class implements all methods of {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService}. It provides database resource handling for all requested resources. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Service -public class RequestedResourceServiceImpl implements RequestedResourceService { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(RequestedResourceServiceImpl.class); - - private RequestedResourceRepository requestedResourceRepository; - private IdsUtils idsUtils; - - private Map requestedResources; - private PolicyHandler policyHandler; - - @Autowired - /** - *

Constructor for RequestedResourceServiceImpl.

- * - * @param requestedResourceRepository a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceRepository} object. - * @param idsUtils a {@link de.fraunhofer.isst.dataspaceconnector.services.IdsUtils} object. - * @param policyHandler a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} object. - */ - public RequestedResourceServiceImpl(RequestedResourceRepository requestedResourceRepository, IdsUtils idsUtils, PolicyHandler policyHandler) { - this.requestedResourceRepository = requestedResourceRepository; - this.idsUtils = idsUtils; - this.policyHandler = policyHandler; - - requestedResources = new HashMap<>(); - for (RequestedResource resource : requestedResourceRepository.findAll()) { - requestedResources.put(resource.getUuid(), idsUtils.getAsResource(resource)); - } - } - - /** - * {@inheritDoc} - * - * Saves the resources with its metadata as external resource or internal resource. - */ - @Override - public UUID addResource(ResourceMetadata resourceMetadata) { - RequestedResource resource = new RequestedResource(new Date(), new Date(), resourceMetadata, "", 0); - - requestedResourceRepository.save(resource); - requestedResources.put(resource.getUuid(), idsUtils.getAsResource(resource)); - - return resource.getUuid(); - } - - /** - * {@inheritDoc} - * - * Publishes the resource data. - */ - @Override - public void addData(UUID resourceId, String data) { - RequestedResource resource = requestedResourceRepository.getOne(resourceId); - - resource.setData(data); - resource.setModified(new Date()); - - requestedResourceRepository.save(resource); - } - - /** - * {@inheritDoc} - * - * Deletes a resource by id. - */ - @Override - public void deleteResource(UUID resourceId) { - requestedResourceRepository.deleteById(resourceId); - requestedResources.remove(resourceId); - } - - /** - * {@inheritDoc} - * - * Gets a resource by id. - */ - @Override - public RequestedResource getResource(UUID resourceId) { - return requestedResourceRepository.getOne(resourceId); - } - - /** - * {@inheritDoc} - * - * Gets resource metadata by id. - */ - @Override - public ResourceMetadata getMetadata(UUID resourceId) { - return requestedResourceRepository.getOne(resourceId).getResourceMetadata(); - } - - /** - * {@inheritDoc} - * - * Gets resource data by id. - */ - @Override - public String getData(UUID resourceId) throws IOException { - RequestedResource resource = requestedResourceRepository.getOne(resourceId); - int counter = resource.getAccessed(); - - resource.setAccessed(counter + 1); - requestedResourceRepository.save(resource); - - if (policyHandler.onDataAccess(resource)) { - return requestedResourceRepository.getOne(resourceId).getData(); - } else { - return "Policy Restriction!"; - } - } - - /** {@inheritDoc} */ - @Override - public ArrayList getRequestedResources() { - return new ArrayList<>(requestedResources.values()); - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyEnforcement.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyEnforcement.java deleted file mode 100644 index 8e5da9d0c..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyEnforcement.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.usagecontrol; - -import de.fraunhofer.iais.eis.Action; -import de.fraunhofer.iais.eis.Contract; -import de.fraunhofer.iais.eis.Duty; -import de.fraunhofer.iais.eis.Permission; -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceRepository; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.DatatypeConfigurationException; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; - -/** - * This class implements automated policy check and usage control enforcement. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -@EnableScheduling -public class PolicyEnforcement { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyEnforcement.class); - - private PolicyVerifier policyVerifier; - - private RequestedResourceService requestedResourceService; - private RequestedResourceRepository requestedResourceRepository; - - private SerializerProvider serializerProvider; - - @Autowired - /** - *

Constructor for PolicyEnforcement.

- * - * @param policyVerifier a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier} object. - * @param requestedResourceService a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService} object. - * @param requestedResourceRepository a {@link de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceRepository} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - */ - public PolicyEnforcement(PolicyVerifier policyVerifier, RequestedResourceService requestedResourceService, - RequestedResourceRepository requestedResourceRepository, SerializerProvider serializerProvider) { - this.policyVerifier = policyVerifier; - this.requestedResourceService = requestedResourceService; - this.requestedResourceRepository = requestedResourceRepository; - this.serializerProvider = serializerProvider; - } - - /** - * Checks all resources every minute. - * 1000 = 1 sec * 60 * 60 = every hour (3600000) - */ - @Scheduled(fixedDelay = 60000) - public void schedule() { - LOGGER.info("Check data..."); - try { - checkResources(); - } catch (ParseException | IOException e) { - LOGGER.error(e.toString()); - } - } - - /** - * Checks all know resources and its policies to delete them if necessary. - * - * @throws java.text.ParseException if any. - * @throws java.io.IOException if any. - */ - public void checkResources() throws ParseException, IOException { - for (RequestedResource resource : requestedResourceRepository.findAll()) { - String policy = resource.getResourceMetadata().getPolicy(); - try { - Contract contract = serializerProvider.getSerializer().deserialize(policy, Contract.class); - if (contract.getPermission() != null && contract.getPermission().get(0) != null) { - Permission permission = contract.getPermission().get(0); - ArrayList postDuties = permission.getPostDuty(); - - if (postDuties != null && postDuties.get(0) != null) { - Action action = postDuties.get(0).getAction().get(0); - if (action == Action.DELETE) { - if (policyVerifier.checkForDelete(postDuties.get(0))) { - requestedResourceService.deleteResource(resource.getUuid()); - } - } - } - } - - } catch (IOException e) { - throw new IOException("The policy could not be read. Please check the policy syntax."); - } - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyHandler.java deleted file mode 100644 index 7706f0e1e..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyHandler.java +++ /dev/null @@ -1,207 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.usagecontrol; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * This class provides policy pattern recognition and calls the {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier} on data request or access. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -public class PolicyHandler { - /** - * Constant LOGGER - */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyHandler.class); - /** Constant contract */ - public static Contract contract; - - private PolicyVerifier policyVerifier; - private SerializerProvider serializerProvider; - - @Autowired - /** - *

Constructor for PolicyHandler.

- * - * @param policyVerifier a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier} object. - * @param serializerProvider a {@link de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider} object. - */ - public PolicyHandler(PolicyVerifier policyVerifier, SerializerProvider serializerProvider) { - this.policyVerifier = policyVerifier; - this.serializerProvider = serializerProvider; - } - - /** - * Reads the properties of an odrl policy to automatically recognize the policy pattern. - * - * @param policy The parsed policy object. - * @return The recognized policy pattern. - * @throws java.io.IOException if any. - */ - public Pattern getPattern(String policy) throws IOException{ - try { - contract = serializerProvider.getSerializer().deserialize(policy, Contract.class); - } catch (IOException e) { - throw new IOException("The policy could not be read. Please check the policy syntax."); - } - - if (contract.getProhibition() != null && contract.getProhibition().get(0) != null) { - return Pattern.PROHIBIT_ACCESS; - } - - if (contract.getPermission() != null && contract.getPermission().get(0) != null) { - Permission permission = contract.getPermission().get(0); - ArrayList constraints = permission.getConstraint(); - ArrayList postDuties = permission.getPostDuty(); - - if (constraints != null && constraints.get(0) != null) { - if (constraints.size() > 1) { - if (postDuties != null && postDuties.get(0) != null) { - return Pattern.USAGE_UNTIL_DELETION; - } else { - return Pattern.USAGE_DURING_INTERVAL; - } - } else { - LeftOperand leftOperand = constraints.get(0).getLeftOperand(); - if (leftOperand == LeftOperand.COUNT) { - return Pattern.N_TIMES_USAGE; - } else if (leftOperand == LeftOperand.ELAPSED_TIME) { - return Pattern.DURATION_USAGE; - } else { - throw new IOException("The recognized policy pattern is not supported by this connector."); - } - } - } else { - if (postDuties != null && postDuties.get(0) != null) { - Action action = postDuties.get(0).getAction().get(0); - if (action == Action.NOTIFY) { - return Pattern.USAGE_NOTIFICATION; - } else if (action == Action.LOG) { - return Pattern.USAGE_LOGGING; - } else { - throw new IOException("The recognized policy pattern is not supported by this connector."); - } - } else { - return Pattern.PROVIDE_ACCESS; - } - } - } else { - throw new IOException("The recognized policy pattern is not supported by this connector."); - } - } - - /** - * Implements the policy restrictions depending on the policy pattern type (on artifact request as provider). - * - * @param policy The resource's usage policy. - * @return Whether the data can be accessed. - * @throws java.io.IOException if any. - */ - public boolean onDataProvision(String policy) throws IOException { - switch (getPattern(policy)) { - case PROVIDE_ACCESS: - return policyVerifier.allowAccess(); - case PROHIBIT_ACCESS: - return policyVerifier.inhibitAccess(); - case USAGE_DURING_INTERVAL: - case USAGE_UNTIL_DELETION: - return policyVerifier.checkInterval(contract); - default: - return true; - } - } - - /** - * Implements the policy restrictions depending on the policy pattern type (on data access as consumer). - * - * @param dataResource The accessed resource. - * @return Whether the data can be accessed. - * @throws java.io.IOException if any. - */ - public boolean onDataAccess(RequestedResource dataResource) throws IOException { - String policy = dataResource.getResourceMetadata().getPolicy(); - - switch (getPattern(policy)) { - case USAGE_DURING_INTERVAL: - case USAGE_UNTIL_DELETION: - return policyVerifier.checkInterval(contract); - case DURATION_USAGE: - return policyVerifier.checkDuration(dataResource.getCreated(), contract); - case USAGE_LOGGING: - return policyVerifier.logAccess(); - case N_TIMES_USAGE: - return policyVerifier.checkFrequency(contract, dataResource.getUuid()); - case USAGE_NOTIFICATION: - return policyVerifier.sendNotification(contract); - default: - return true; - } - } - - public enum Pattern { - /** - * Standard pattern to allow unrestricted access. - */ - PROVIDE_ACCESS("PROVIDE_ACCESS"), - /** - * Default pattern if no other is detected. - * v2.0: NO_POLICY("no-policy") - */ - PROHIBIT_ACCESS("PROHIBIT_ACCESS"), - /** - * Type: NotMoreThanN - * v2.0: COUNT_ACCESS("count-access") - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/NTimesUsageTemplates/N_TIMES_USAGE_OFFER_TEMPLATE.jsonld - */ - N_TIMES_USAGE("N_TIMES_USAGE"), - /** - * Type: DurationOffer - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/TimeRestrictedUsageTemplates/DURATION_USAGE_OFFER_TEMPLATE.jsonld - */ - DURATION_USAGE("DURATION_USAGE"), - /** - * Type: IntervalUsage - * v2.0: TIME_INTERVAL("time-interval") - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/TimeRestrictedUsageTemplates/USAGE_DURING_INTERVAL_OFFER_TEMPLATE.jsonld - */ - USAGE_DURING_INTERVAL("USAGE_DURING_INTERVAL"), - /** - * Type: DeleteAfterInterval - * v2.0: DELETE_AFTER("delete-after") - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/TimeRestrictedUsageTemplates/USAGE_UNTIL_DELETION_OFFER_TEMPLATE.jsonld - */ - USAGE_UNTIL_DELETION("USAGE_UNTIL_DELETION"), - /** - * Type: Logging - * v2.0: LOG_ACCESS("log-access") - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/UsageLoggingTemplates/USAGE_LOGGING_OFFER_TEMPLATE.jsonld - */ - USAGE_LOGGING("USAGE_LOGGING"), - /** - * Type: Notification - * https://github.com/International-Data-Spaces-Association/InformationModel/blob/master/examples/contracts-and-usage-policy/templates/UsageNotificationTemplates/USAGE_NOTIFICATION_OFFER_TEMPLATE.jsonld - */ - USAGE_NOTIFICATION("USAGE_NOTIFICATION"); - - private final String pattern; - - Pattern(String string) { - pattern = string; - } - - @Override - public String toString() { - return pattern; - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyReader.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyReader.java deleted file mode 100644 index e6c03311f..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyReader.java +++ /dev/null @@ -1,200 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.usagecontrol; - -import de.fraunhofer.iais.eis.BinaryOperator; -import de.fraunhofer.iais.eis.Constraint; -import de.fraunhofer.iais.eis.Rule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.Duration; -import java.net.URI; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -/** - * This class reads the content of the policy rules and returns needed information to the {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier}. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -public class PolicyReader { - /** - * Constant LOGGER - */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyReader.class); - - /** - * Gets the access frequency of a policy. - * - * @param rule The policy rule object. - * @return The time frequency. - */ - public Integer getMaxAccess(Rule rule) { - Constraint constraint = rule.getConstraint().get(0); - - int value = Integer.parseInt(constraint.getRightOperand().getValue()); - switch (constraint.getOperator()) { - case EQ: - case LTEQ: - return value; - case LT: - return value - 1; - default: - return 0; - } - } - - /** - * Gets the time interval of a policy. - * - * @param rule The policy rule object. - * @return The time interval. - */ - public TimeInterval getTimeInterval(Rule rule) { - TimeInterval timeInterval = new TimeInterval(); - - for (Constraint constraint : rule.getConstraint()) { - if (constraint.getOperator() == BinaryOperator.AFTER) { - timeInterval.setStart(constraint.getRightOperand().getValue()); - } else if (constraint.getOperator() == BinaryOperator.BEFORE) { - timeInterval.setEnd(constraint.getRightOperand().getValue()); - } - } - return timeInterval; - } - - /** - * Gets the log path value of a policy. - * - * @param rule The policy rule object. - * @return The found value. - */ - public String getEndpoint(Rule rule) { - Constraint constraint = rule.getConstraint().get(0); - return constraint.getRightOperand().getValue(); - } - - /** - * Gets the log path value of a policy. - * - * @param rule The policy rule object. - * @return The found value. - */ - public URI getPipEndpoint(Rule rule) { - Constraint constraint = rule.getConstraint().get(0); - return constraint.getPipEndpoint(); - } - - /** - * Gets the date value of a policy. - * - * @param rule The policy constraint object. - * @return The date or null. - * @throws java.text.ParseException if any. - */ - public Date getDate(Rule rule) throws ParseException { - Constraint constraint = rule.getConstraint().get(0); - String date = constraint.getRightOperand().getValue(); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - sdf.setTimeZone(Calendar.getInstance().getTimeZone()); - return sdf.parse(date); - } - - /** - * Gets the duration value of a policy. - * - * @param rule The policy constraint object. - * @return The duration or null. - * @throws javax.xml.datatype.DatatypeConfigurationException if any. - */ - public Duration getDuration(Rule rule) throws DatatypeConfigurationException { - Constraint constraint = rule.getConstraint().get(0); - if (constraint.getRightOperand().getType().equals("xsd:duration")) { - String duration = constraint.getRightOperand().getValue(); - return DatatypeFactory.newInstance().newDuration(duration); - } else { - return null; - } - } - - /** - *

TimeInterval class.

- * - * @author Julia Pampus - * @version $Id: $Id - */ - public static class TimeInterval { - private String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - private Date start; - private Date end; - - /** - *

Constructor for TimeInterval.

- */ - public TimeInterval() { - } - - /** - *

Constructor for TimeInterval.

- * - * @param start a {@link Date} object. - * @param end a {@link Date} object. - */ - public TimeInterval(Date start, Date end) { - this.start = start; - this.end = end; - } - - /** - *

Getter for the field start.

- * - * @return a {@link Date} object. - */ - public Date getStart() { - return start; - } - - /** - *

Setter for the field start.

- * - * @param start a {@link String} object. - */ - public void setStart(String start) { - try { - this.start = new SimpleDateFormat(DATE_FORMAT_PATTERN).parse(start); - } catch (ParseException e) { - e.printStackTrace(); - } - } - - /** - *

Getter for the field end.

- * - * @return a {@link Date} object. - */ - public Date getEnd() { - return end; - } - - /** - *

Setter for the field end.

- * - * @param end a {@link String} object. - */ - public void setEnd(String end) { - try { - this.end = new SimpleDateFormat(DATE_FORMAT_PATTERN).parse(end); - } catch (ParseException e) { - e.printStackTrace(); - } - } - } -} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyVerifier.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyVerifier.java deleted file mode 100644 index 58da925cf..000000000 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyVerifier.java +++ /dev/null @@ -1,208 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.services.usagecontrol; - -import de.fraunhofer.iais.eis.Contract; -import de.fraunhofer.iais.eis.Rule; -import de.fraunhofer.isst.dataspaceconnector.services.HttpUtils; -import de.fraunhofer.isst.dataspaceconnector.services.communication.MessageService; -import de.fraunhofer.isst.ids.framework.exceptions.HttpClientException; -import okhttp3.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.Duration; -import java.io.IOException; -import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.UUID; - -/** - * This class provides access permission information for the {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} depending on the policy content. - * - * @author Julia Pampus - * @version $Id: $Id - */ -@Component -public class PolicyVerifier { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyVerifier.class); - - private PolicyReader policyReader; - private MessageService messageService; - private HttpUtils httpUtils; - - @Autowired - /** - *

Constructor for PolicyVerifier.

- * - * @param policyReader a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyReader} object. - * @param httpUtils a {@link de.fraunhofer.isst.dataspaceconnector.services.HttpUtils} object. - * @param messageService a {@link de.fraunhofer.isst.dataspaceconnector.services.communication.MessageService} object. - */ - public PolicyVerifier(PolicyReader policyReader, MessageService messageService, HttpUtils httpUtils) { - this.policyReader = policyReader; - this.messageService = messageService; - this.httpUtils = httpUtils; - } - - /** - * Allows data access. - * - * @return Access allowed. - */ - public boolean allowAccess() { - return true; - } - - /** - * Inhibits data access. - * - * @return Access denied. - */ - public boolean inhibitAccess() { - return false; - } - - /** - * Saves the access date into the database. - * - * @return Success or not (access or inhibition). - */ - public boolean logAccess() { - try { - Response response = messageService.sendLogMessage(); - if (response != null && response.code() == 200) { - return allowAccess(); - } else { - LOGGER.error("NOT LOGGED"); - return allowAccess(); - } - } catch (HttpClientException | IOException e) { - LOGGER.error(e.getMessage()); - return inhibitAccess(); - } - } - - /** - * Notify participant about data access. - * - * @return Success or not (access or inhibition). - * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. - */ - public boolean sendNotification(Contract contract) { - Rule rule = contract.getPermission().get(0).getPostDuty().get(0); - String recipient = policyReader.getEndpoint(rule); - - try { - Response response = messageService.sendNotificationMessage(recipient); - if (response != null && response.code() == 200) { - return allowAccess(); - } else { - LOGGER.error("NOT NOTIFIED"); - return allowAccess(); - } - } catch (HttpClientException | IOException e) { - LOGGER.error(e.getMessage()); - return inhibitAccess(); - } - } - - /** - * Checks if the requested access is in the allowed time interval. - * - * @return If this is the case, access is provided. - * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. - */ - public boolean checkInterval(Contract contract) { - PolicyReader.TimeInterval timeInterval = policyReader.getTimeInterval(contract.getPermission().get(0)); - Date date = new Date(); - - if (date.after(timeInterval.getStart()) && date.before(timeInterval.getEnd())) { - return allowAccess(); - } else { - return inhibitAccess(); - } - } - - /** - * Checks whether the current date is later than the specified one. - * - * @param dateNow The current date. - * @param maxAccess The target date. - * @return True if the date has been already exceeded, false if not. - */ - public boolean checkDate(Date dateNow, Date maxAccess) { - return dateNow.after(maxAccess); - } - - /** - * Adds a duration to a date to get the a date. - * - * @param created The date when the resource was created. - * @return True if the resource should be deleted, false if not. - * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. - */ - public boolean checkDuration(Date created, Contract contract) { - Calendar cal = Calendar.getInstance(); - cal.setTime(created); - try { - Duration duration = policyReader.getDuration(contract.getPermission().get(0)); - - cal.add(Calendar.SECOND, duration.getSeconds()); - cal.add(Calendar.MINUTE, duration.getMinutes()); - cal.add(Calendar.HOUR_OF_DAY, duration.getHours()); - cal.add(Calendar.DAY_OF_MONTH, duration.getDays()); - cal.add(Calendar.MONTH, duration.getMonths()); - cal.add(Calendar.YEAR, duration.getYears()); - - return !checkDate(new Date(), cal.getTime()); - } catch (DatatypeConfigurationException e) { - return inhibitAccess(); - } - } - - /** - * Checks whether the maximum of access number is already reached. - * - * @return If this is not the case, access is provided. Otherwise, data is deleted and access denied. - * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. - * @param uuid a {@link java.util.UUID} object. - */ - public boolean checkFrequency(Contract contract, UUID uuid) { - int max = policyReader.getMaxAccess(contract.getPermission().get(0)); - URI pip = policyReader.getPipEndpoint(contract.getPermission().get(0)); - - try { - String accessed = httpUtils.sendHttpsGetRequestWithBasicAuth(pip + uuid.toString() + "/access", "admin", "password"); - if (Integer.parseInt(accessed) > max) { - return inhibitAccess(); - } else { - return allowAccess(); - } - } catch (IOException | KeyManagementException | NoSuchAlgorithmException e) { - return inhibitAccess(); - } - } - - /** - * Checks if the duration since resource creation or the max date for resource access has been already exceeded. - * - * @return True if the resource should be deleted, false if not. - * @param rule a {@link de.fraunhofer.iais.eis.Rule} object. - * @throws java.text.ParseException if any. - */ - public boolean checkForDelete(Rule rule) throws ParseException { - Date max = policyReader.getDate(rule); - if (max != null) { - return checkDate(new Date(), max); - } else { - return false; - } - } -} diff --git a/src/main/java/io/dataspaceconnector/ConnectorApplication.java b/src/main/java/io/dataspaceconnector/ConnectorApplication.java new file mode 100644 index 000000000..ffecda84e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/ConnectorApplication.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; + +/** + * This is the main application class. The application is started and an openApi bean for the + * Swagger UI is created. + */ + +@SpringBootApplication +@ComponentScan({ + "de.fraunhofer.isst.ids.framework.messaging", + "io.dataspaceconnector", + "de.fraunhofer.isst.ids.framework.communication", + "de.fraunhofer.isst.ids.framework.configuration", + "de.fraunhofer.isst.ids.framework.daps" +}) +public class ConnectorApplication { + + /** + * The main method. + * @param args List of arguments. + */ + public static void main(final String[] args) { + SpringApplication.run(ConnectorApplication.class, args); + } + + /** + * Creates the OpenAPI main description. The description contains general project information + * such as e.g. title, version and contact information. + * + * @return The OpenAPI description. + * @throws IOException Throws an exception if the properties cannot be loaded from file. + */ + @Bean + public OpenAPI customOpenAPI() throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = getClass().getClassLoader() + .getResourceAsStream("application.properties")) { + // This function may crash (e.g. ill-formatted file). Let it bubble up. + properties.load(inputStream); + } + + return new OpenAPI() + .components(new Components()) + .info(new Info() + .title(properties.getProperty("title")) + .description(properties.getProperty("project_desc")) + .version(properties.getProperty("version")) + .contact(new Contact() + .name(properties.getProperty("organization_name")) + .url(properties.getProperty("contact_url")) + .email(properties.getProperty("contact_email")) + ) + .license(new License() + .name(properties.getProperty("licence")) + .url(properties.getProperty("licence_url"))) + ); + } +} diff --git a/src/main/java/io/dataspaceconnector/config/ConfigurationAdapter.java b/src/main/java/io/dataspaceconnector/config/ConfigurationAdapter.java new file mode 100644 index 000000000..b9296cb5d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/ConfigurationAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; + +/** + * This class configures admin rights for all backend endpoints behind "/api" using the role + * defined in {@link MultipleEntryPointsSecurityConfig}. + */ +@Log4j2 +@Configuration +public class ConfigurationAdapter extends WebSecurityConfigurerAdapter { + + /** + * Whether the h2 console is enabled. + */ + @Value("${spring.h2.console.enabled}") + private boolean isH2ConsoleEnabled; + + @Override + protected final void configure(final HttpSecurity http) throws Exception { + http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/ids/data").anonymous() + .antMatchers("/").anonymous() + .antMatchers("/api/**").authenticated() + .antMatchers("/actuator/**").hasRole("ADMIN") + .antMatchers("/database/**").hasRole("ADMIN") + .anyRequest().authenticated() + .and() + .csrf().disable() + .httpBasic() + .authenticationEntryPoint(authenticationEntryPoint()); + http.headers().xssProtection(); + + if (isH2ConsoleEnabled) { + http.headers().frameOptions().disable(); + if (log.isWarnEnabled()) { + log.warn("H2 Console enabled. Disabling frame protection."); + } + } else { + http.headers().frameOptions().deny(); + } + } + + /** + * Bean with an entry point for the admin realm. + * + * @return The authentication entry point for the admin realm. + */ + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + final var entryPoint = new BasicAuthenticationEntryPoint(); + entryPoint.setRealmName("admin realm"); + return entryPoint; + } +} diff --git a/src/main/java/io/dataspaceconnector/config/ConnectionConfiguration.java b/src/main/java/io/dataspaceconnector/config/ConnectionConfiguration.java new file mode 100644 index 000000000..d4edf7b47 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/ConnectionConfiguration.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import de.fraunhofer.isst.ids.framework.communication.http.HttpService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.time.Duration; + +/** + * This class handles connection settings for outgoing http connections. + */ +@Configuration +@RequiredArgsConstructor +public class ConnectionConfiguration { + + /** + * Global timeout value. + */ + @Value("${http.timeout.connect}") + private long connectTimeout; + + /** + * Read timeout value. + */ + @Value("${http.timeout.read}") + private long readTimeout; + + /** + * Write timeout value. + */ + @Value("${http.timeout.write}") + private long writeTimeout; + + /** + * Call timeout value. + */ + @Value("${http.timeout.call}") + private long callTimeout; + + /** + * Service for http connections. + */ + private final @NonNull HttpService httpService; + + /** + * Hand over connection settings from the application.properties to the http client. Either the + * three values connect, read, and write are used, or the global call timeout. + */ + @PostConstruct + public void setTimeouts() { + if (connectTimeout != 0 && readTimeout != 0 && writeTimeout != 0) { + httpService.setTimeouts( + Duration.ofMillis(connectTimeout), + Duration.ofMillis(readTimeout), + Duration.ofMillis(writeTimeout), + null); + } else { + httpService.setTimeouts(null, null, null, + Duration.ofMillis(callTimeout)); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/config/ConnectorConfiguration.java b/src/main/java/io/dataspaceconnector/config/ConnectorConfiguration.java new file mode 100644 index 000000000..e0a95b1cd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/ConnectorConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.net.URI; + +/** + * This class handles policy settings: negotiation, pattern support, and usage control framework. + */ +@Data +@Configuration +public class ConnectorConfiguration { + /** + * The clearing house access url. + */ + @Value("${clearing.house.url}") + private URI clearingHouse; + + /** + * The policy negotiation status from application.properties. + */ + @Value("${policy.negotiation}") + private boolean policyNegotiation; + + /** + * Setting for allowing unsupported patterns from application.properties. + */ + @Value("${policy.allow-unsupported-patterns}") + private boolean allowUnsupported; + + /** + * Usage control framework from application.properties. + */ + @Value("${policy.framework}") + private UsageControlFramework ucFramework; +} diff --git a/src/main/java/io/dataspaceconnector/config/DapsTokenMethodSecurityConfig.java b/src/main/java/io/dataspaceconnector/config/DapsTokenMethodSecurityConfig.java new file mode 100644 index 000000000..0df8ed07f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/DapsTokenMethodSecurityConfig.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +/** + * Configuration for checking if a daps token is valid before entering a function. + */ +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class DapsTokenMethodSecurityConfig extends GlobalMethodSecurityConfiguration { + + /** + * Validator for DAT. + */ + private DapsTokenValidator tokenValidator; + + /** + * Set the token provider. + * @param validator The token validator. + */ + @Autowired + public void setTokenValidator(final DapsTokenValidator validator) { + this.tokenValidator = validator; + } + + /** + * Create the expression handler using the validator. + */ + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + final var expressionHandler = new DefaultMethodSecurityExpressionHandler(); + expressionHandler.setPermissionEvaluator(tokenValidator); + + return expressionHandler; + } +} diff --git a/src/main/java/io/dataspaceconnector/config/DapsTokenValidator.java b/src/main/java/io/dataspaceconnector/config/DapsTokenValidator.java new file mode 100644 index 000000000..13f6ae526 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/DapsTokenValidator.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import java.io.Serializable; + +/** + * This class provides DAT validation. + */ +@Component +@AllArgsConstructor +public final class DapsTokenValidator implements PermissionEvaluator { + + /** + * Service for providing current DAT. + */ + private final @NonNull DapsTokenProvider tokenProvider; + + @Override + public boolean hasPermission( + final Authentication auth, final Object targetDomainObject, final Object permission) { + if ((auth == null) || (targetDomainObject == null) || !(permission instanceof String)) { + return false; + } + + return hasPrivilege(); + } + + @Override + public boolean hasPermission(final Authentication auth, final Serializable targetId, + final String targetType, final Object permission) { + if ((auth == null) || (targetType == null) || !(permission instanceof String)) { + return false; + } + + return hasPrivilege(); + } + + private boolean hasPrivilege() { + if (tokenProvider == null) { + return false; + } else { + return tokenProvider.getDAT() != null; + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java b/src/main/java/io/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java similarity index 55% rename from src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java rename to src/main/java/io/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java index 3fe8513c4..47ad574a6 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java +++ b/src/main/java/io/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java @@ -1,4 +1,19 @@ -package de.fraunhofer.isst.dataspaceconnector.config; +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -11,29 +26,33 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** - * This class creates an admin role for spring basic security setup used in {@link de.fraunhofer.isst.dataspaceconnector.config.ConfigurationAdapter}. - * - * @author Julia Pampus - * @version $Id: $Id + * This class creates an admin role for spring basic security setup used in {@link + * ConfigurationAdapter}. */ @Configuration @EnableWebSecurity public class MultipleEntryPointsSecurityConfig { + /** + * Username defined in application.properties. + */ @Value("${spring.security.user.name}") private String username; + /** + * Password defined in application.properties. + */ @Value("${spring.security.user.password}") private String password; /** - *

userDetailsService.

+ * Bean setting up an default admin. * - * @return a {@link org.springframework.security.core.userdetails.UserDetailsService} object. + * @return The password encoder. */ @Bean public UserDetailsService userDetailsService() { - InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + final var manager = new InMemoryUserDetailsManager(); manager.createUser(User .withUsername(username) .password(encoder().encode(password)) @@ -42,9 +61,9 @@ public UserDetailsService userDetailsService() { } /** - *

encoder.

+ * Bean providing a password encoder. * - * @return a {@link org.springframework.security.crypto.password.PasswordEncoder} object. + * @return The password encoder. */ @Bean public PasswordEncoder encoder() { diff --git a/src/main/java/io/dataspaceconnector/config/RestConfiguration.java b/src/main/java/io/dataspaceconnector/config/RestConfiguration.java new file mode 100644 index 000000000..664e96998 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/RestConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.config.EnableHypermediaSupport; + +/** + * Configures HAL support for API responses. + */ +@Configuration +@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) +public class RestConfiguration { +} diff --git a/src/main/java/io/dataspaceconnector/config/UsageControlFramework.java b/src/main/java/io/dataspaceconnector/config/UsageControlFramework.java new file mode 100644 index 000000000..53f2ef3ae --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/UsageControlFramework.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +/** + * This class provides a usage control framework enum. + */ +public enum UsageControlFramework { + + /** + * Usage control (enforcement) inside the connector. + */ + INTERNAL("INTERNAL"), + + /** + * Usage control framework MyData. + */ + MY_DATA("MY_DATA"); + + /** + * The usage control framework. + */ + private final String framework; + + UsageControlFramework(final String string) { + framework = string; + } + + @Override + public String toString() { + return framework; + } +} diff --git a/src/main/java/io/dataspaceconnector/config/package-info.java b/src/main/java/io/dataspaceconnector/config/package-info.java new file mode 100644 index 000000000..e167842fa --- /dev/null +++ b/src/main/java/io/dataspaceconnector/config/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The configurations for spring. + */ +package io.dataspaceconnector.config; diff --git a/src/main/java/io/dataspaceconnector/controller/ConfigurationController.java b/src/main/java/io/dataspaceconnector/controller/ConfigurationController.java new file mode 100644 index 000000000..62d381fae --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/ConfigurationController.java @@ -0,0 +1,192 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.utils.ControllerUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.minidev.json.JSONObject; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class provides endpoints for connector configurations via a connected config manager. + */ +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class ConfigurationController { + + /** + * The current connector configuration. + */ + private final @NonNull ConfigurationContainer configContainer; + + /** + * The current policy configuration. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Service for deserializing ids objects. + */ + private final @NonNull DeserializationService idsService; + + /** + * Update the connector's current configuration. + * + * @param configuration The new configuration. + * @return Ok or error response. + */ + @PutMapping(value = "/configuration", consumes = {"application/json", "application/ld+json"}, + produces = {"application/ld+json"}) + @Operation(summary = "Update current configuration.") + @Tag(name = "Connector", description = "Endpoints for connector information and configuration") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Failed to deserialize."), + @ApiResponse(responseCode = "415", description = "Wrong media type."), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + public ResponseEntity updateConfiguration(@RequestBody final String configuration) { + try { + // Deserialize input. + final var config = idsService.getConfigurationModel(configuration); + + // Update configuration of connector. + configContainer.updateConfiguration(config); + return getConfiguration(); + } catch (ConfigurationUpdateException exception) { + return ControllerUtils.respondConfigurationUpdateError(exception); + } catch (IllegalArgumentException exception) { + return ControllerUtils.respondDeserializationError(exception); + } + } + + /** + * Return the connector's current configuration. + * + * @return The configuration object or an error. + */ + @GetMapping(value = "/configuration", produces = "application/json") + @Operation(summary = "Get current configuration.") + @Tag(name = "Connector", description = "Endpoints for connector information and configuration") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found")}) + @ResponseBody + public ResponseEntity getConfiguration() { + final var config = configContainer.getConfigModel(); + if (config == null) { + return ControllerUtils.respondConfigurationNotFound(); + } else { + return ResponseEntity.ok(config.toRdf()); + } + } + + /** + * Turns contract negotiation on or off (at runtime). + * + * @param status The desired state. + * @return Http ok or error response. + */ + @PutMapping(value = "/configuration/negotiation", produces = "application/json") + @Operation(summary = "Set contract negotiation status") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + @ResponseBody + public ResponseEntity setNegotiationStatus( + @RequestParam("status") final boolean status) { + connectorConfig.setPolicyNegotiation(status); + return getNegotiationStatus(); + } + + /** + * Returns the contract negotiation status. + * + * @return Http ok or error response. + */ + @GetMapping(value = "/configuration/negotiation", produces = "application/json") + @Operation(summary = "Get contract negotiation status") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + @ResponseBody + public ResponseEntity getNegotiationStatus() { + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + final var body = new JSONObject(); + body.put("status", connectorConfig.isPolicyNegotiation()); + + return new ResponseEntity<>(body, headers, HttpStatus.OK); + } + + /** + * Allows requesting data without policy enforcement. + * + * @param status The desired state. + * @return Http ok or error response. + */ + @PutMapping(value = "/configuration/pattern", produces = "application/json") + @Operation(summary = "Allow unsupported patterns", description = "Allow " + + "requesting data without policy enforcement if an unsupported pattern is recognized.") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + @ResponseBody + public ResponseEntity setPatternStatus( + @RequestParam("status") final boolean status) { + connectorConfig.setAllowUnsupported(status); + return getPatternStatus(); + } + + /** + * Returns the unsupported pattern status. + * + * @return Http ok or error response. + */ + @GetMapping(value = "/configuration/pattern", produces = "application/json") + @Operation(summary = "Get pattern validation status", + description = "Return if unsupported patterns are ignored when requesting data.") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + @ResponseBody + public ResponseEntity getPatternStatus() { + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + final var body = new JSONObject(); + body.put("status", connectorConfig.isAllowUnsupported()); + + return new ResponseEntity<>(body, headers, HttpStatus.OK); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/ExampleController.java b/src/main/java/io/dataspaceconnector/controller/ExampleController.java new file mode 100644 index 000000000..154173dfd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/ExampleController.java @@ -0,0 +1,196 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; + +import de.fraunhofer.iais.eis.BaseConnectorBuilder; +import de.fraunhofer.iais.eis.BasicAuthenticationBuilder; +import de.fraunhofer.iais.eis.ConfigurationModelBuilder; +import de.fraunhofer.iais.eis.ConnectorDeployMode; +import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +import de.fraunhofer.iais.eis.ConnectorStatus; +import de.fraunhofer.iais.eis.KeyType; +import de.fraunhofer.iais.eis.LogLevel; +import de.fraunhofer.iais.eis.ProxyBuilder; +import de.fraunhofer.iais.eis.PublicKeyBuilder; +import de.fraunhofer.iais.eis.SecurityProfile; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.usagecontrol.PolicyPattern; +import io.dataspaceconnector.utils.ControllerUtils; +import io.dataspaceconnector.utils.PatternUtils; +import io.dataspaceconnector.utils.RuleUtils; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class provides endpoints exposing example resources and configurations. + */ +@RestController +@RequestMapping("/api/examples") +@RequiredArgsConstructor +public class ExampleController { + /** + * Policy management point. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Get an example configuration for the config.json. + * + * @return a {@link org.springframework.http.ResponseEntity} object. + */ + @Hidden + @Operation(summary = "Get sample connector configuration", + description = "Get a sample connector configuration for the config.json.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @GetMapping("/configuration") + @ResponseBody + public ResponseEntity getConnectorConfiguration() { + return ResponseEntity.ok( + new ConfigurationModelBuilder(URI.create("configId")) + ._configurationModelLogLevel_(LogLevel.NO_LOGGING) + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._connectorProxy_(Util.asList( + new ProxyBuilder(URI.create("proxiId")) + ._noProxy_(new ArrayList<>(Collections.singletonList( + URI.create("https://localhost:8080/")))) + ._proxyAuthentication_( + new BasicAuthenticationBuilder( + URI.create("basicAuthId")).build()) + ._proxyURI_(URI.create( + "proxy.dortmund.isst.fraunhofer.de:3128")) + .build())) + ._connectorStatus_(ConnectorStatus.CONNECTOR_ONLINE) + ._connectorDescription_( + new BaseConnectorBuilder(URI.create("connectorId")) + ._maintainer_(URI.create("https://example.com")) + ._curator_(URI.create("https://example.com")) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + ._outboundModelVersion_("4.0.0") + ._inboundModelVersion_(Util.asList("4.0.0")) + ._title_(Util.asList( + new TypedLiteral("Dataspace Connector"))) + ._description_(Util.asList(new TypedLiteral( + "IDS Connector with static " + + "example resources hosted by the Fraunhofer " + + "ISST."))) + ._version_("v3.0.0") + ._publicKey_( + new PublicKeyBuilder(URI.create("keyId")) + ._keyType_(KeyType.RSA) + ._keyValue_( + "Your daps token here.".getBytes( + StandardCharsets.UTF_8)) + .build()) + ._hasDefaultEndpoint_( + new ConnectorEndpointBuilder( + URI.create("endpointId")) + ._accessURL_(URI.create("/api/ids/data")) + .build()) + .build()) + ._keyStore_(URI.create("file:///conf/keystore.p12")) + ._trustStore_(URI.create("file:///conf/truststore.p12")) + .build().toRdf()); + } + + /** + * Validate a rule and get the policy pattern. + * + * @param ruleAsString Policy as string. + * @return A pattern enum or error. + */ + @Operation(summary = "Get pattern of policy", + description = "Get the policy pattern represented by a given JSON string.") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) + @PostMapping("/validation") + @ResponseBody + public ResponseEntity getPolicyPattern( + @Parameter(description = "The JSON string representing a policy", required = true) + @RequestBody final String ruleAsString) { + try { + final var rule = deserializationService.getRule(ruleAsString); + return ResponseEntity.ok(RuleUtils.getPatternByRule(rule)); + } catch (IllegalStateException | ContractException exception) { + return ControllerUtils.respondPatternNotIdentified(exception); + } + } + + /** + * Get an example policy pattern. + * + * @param pattern Policy pattern type. + * @return An example policy object that can be filled out. + */ + @Operation(summary = "Get example policy", + description = "Get an example policy for a given policy pattern.") + @Tag(name = "Usage Control", description = "Endpoints for contract/policy handling") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Bad Request") }) + @PostMapping("/policy") + @ResponseBody + public ResponseEntity getExampleUsagePolicy( + @Parameter(description = "Selection of supported policy patterns.", required = true) + @RequestParam("type") final PolicyPattern pattern) { + switch (pattern) { + case PROVIDE_ACCESS: + return ResponseEntity.ok(PatternUtils.buildProvideAccessRule()); + case PROHIBIT_ACCESS: + return ResponseEntity.ok(PatternUtils.buildProhibitAccessRule()); + case N_TIMES_USAGE: + return ResponseEntity.ok(PatternUtils.buildNTimesUsageRule()); + case DURATION_USAGE: + return ResponseEntity.ok(PatternUtils.buildDurationUsageRule()); + case USAGE_DURING_INTERVAL: + return ResponseEntity.ok(PatternUtils.buildIntervalUsageRule()); + case USAGE_UNTIL_DELETION: + return ResponseEntity.ok(PatternUtils.buildUsageUntilDeletionRule()); + case USAGE_LOGGING: + return ResponseEntity.ok(PatternUtils.buildUsageLoggingRule()); + case USAGE_NOTIFICATION: + return ResponseEntity.ok(PatternUtils.buildUsageNotificationRule()); + case CONNECTOR_RESTRICTED_USAGE: + return ResponseEntity.ok(PatternUtils.buildConnectorRestrictedUsageRule()); + default: + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/GlobalExceptionHandler.java b/src/main/java/io/dataspaceconnector/controller/GlobalExceptionHandler.java new file mode 100644 index 000000000..f73ec5778 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/GlobalExceptionHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import lombok.extern.log4j.Log4j2; +import net.minidev.json.JSONObject; + +/** + * Controller for global handling of runtime exceptions. + */ +@Log4j2 +@ControllerAdvice +@Order(Ordered.LOWEST_PRECEDENCE) +public final class GlobalExceptionHandler { + /** + * Handle runtime exceptions with response code 500. + * + * @param exception The thrown exception. + * @return Response entity with code 500. + */ + @ExceptionHandler(value = {Exception.class}) + public ResponseEntity handleAnyException(final RuntimeException exception) { + if (log.isErrorEnabled()) { + log.error("An unhandled exception has been caught. [exception=({})]", + exception != null ? exception.getMessage() : "Passed null as exception", + exception); + } + + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-Error", "true"); + + final var body = new JSONObject(); + body.put("message", "An error occurred. Please try again later."); + + return new ResponseEntity<>(body, headers, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandler.java b/src/main/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandler.java new file mode 100644 index 000000000..9b8198e00 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.log4j.Log4j2; + +import net.minidev.json.JSONObject; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * This class handles exceptions of type {@link JsonProcessingException}. + */ +@RestControllerAdvice +@Log4j2 +@Order(1) +public class JsonProcessingExceptionHandler { + + /** + * Handles thrown {@link JsonProcessingException}. + * + * @param exception The thrown exception. + * @return A http response. + */ + @ExceptionHandler(JsonProcessingException.class) + public ResponseEntity handleJsonProcessingException( + final JsonProcessingException exception) { + if (log.isWarnEnabled()) { + log.warn("Invalid input. [exception=({})]", exception == null ? "" + : exception.getMessage()); + } + + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + final var body = new JSONObject(); + body.put("message", "Invalid input."); + + return new ResponseEntity<>(body, headers, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/MainController.java b/src/main/java/io/dataspaceconnector/controller/MainController.java new file mode 100644 index 000000000..3a74c82f3 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/MainController.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +/** + * This class provides endpoints for basic connector services. + */ +@RestController +@Tag(name = "Connector", description = "Endpoints for connector information and configuration") +@RequiredArgsConstructor +public class MainController { + + /** + * Service for ids connector management. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Gets connector self-description without catalogs and resources. + * + * @return Self-description or error response. + */ + @GetMapping(value = {"/", ""}, produces = "application/ld+json") + @Operation(summary = "Public IDS self-description") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok")}) + @ResponseBody + public ResponseEntity getPublicSelfDescription() { + return ResponseEntity.ok(connectorService.getConnectorWithoutResources().toRdf()); + } + + /** + * Gets connector self-description with all resources. + * + * @return Self-description or error response. + */ + @GetMapping(value = "/api/connector", produces = "application/ld+json") + @Operation(summary = "Private IDS self-description") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + public ResponseEntity getPrivateSelfDescription() { + return ResponseEntity.ok(connectorService.getConnectorWithOfferedResources().toRdf()); + } + + /** + * Provides links at root page. + * + * @return Http ok. + */ + @Hidden + @GetMapping("/api") + public ResponseEntity root() { + final var model = new RepresentationModel(); + + model.add(linkTo(methodOn(MainController.class).root()).withSelfRel()); + model.add(linkTo(methodOn(ResourceControllers.AgreementController.class) + .getAll(null, null)).withRel("agreements")); + model.add(linkTo(methodOn(ResourceControllers.ArtifactController.class) + .getAll(null, null)).withRel("artifacts")); + model.add(linkTo(methodOn(ResourceControllers.CatalogController.class) + .getAll(null, null)).withRel("catalogs")); + model.add(linkTo(methodOn(ResourceControllers.ContractController.class) + .getAll(null, null)).withRel("contracts")); + model.add(linkTo(methodOn(ResourceControllers.OfferedResourceController.class) + .getAll(null, null)).withRel("offers")); + model.add(linkTo(methodOn(ResourceControllers.RepresentationController.class) + .getAll(null, null)).withRel("representations")); + model.add(linkTo(methodOn(ResourceControllers.RequestedResourceController.class) + .getAll(null, null)).withRel("requests")); + model.add(linkTo(methodOn(ResourceControllers.RuleController.class) + .getAll(null, null)).withRel("rules")); + + return ResponseEntity.ok(model); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandler.java b/src/main/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandler.java new file mode 100644 index 000000000..697f9916f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import lombok.extern.log4j.Log4j2; +import net.minidev.json.JSONObject; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * Controller for handling {@link + * io.dataspaceconnector.exceptions.ResourceNotFoundException}. + */ +@ControllerAdvice +@Log4j2 +@Order(1) +public final class PolicyRestrictionExceptionHandler { + /** + * Handle {@link PolicyRestrictionException}. + * + * @param exception The thrown exception. + * @return Response entity with code 404. + */ + @ExceptionHandler(PolicyRestrictionException.class) + public ResponseEntity handlePolicyRestrictionException( + final PolicyRestrictionException exception) { + if (log.isDebugEnabled()) { + log.debug("Policy restriction detected. [exception=({})]", exception == null + ? "" : exception.getMessage(), exception); + } + + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + final var body = new JSONObject(); + body.put("message", "A policy restriction has been detected."); + + return new ResponseEntity<>(body, headers, HttpStatus.FORBIDDEN); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageController.java new file mode 100644 index 000000000..65284443f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageController.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.utils.ControllerUtils; +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.Objects; + +/** + * Controller for sending ids connector unavailable messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class ConnectorUnavailableMessageController { + + /** + * The service for communication with the ids broker. + */ + private final @NonNull IDSBrokerService brokerService; + + /** + * Service for the current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Sending an ids connector unavailable message with the current connector as payload. + * TODO Validate response message and return OK or other status code. + * + * @param recipient The url of the recipient. + * @return The response message or an error. + */ + @PostMapping("/connector/unavailable") + @Operation(summary = "Connector unavailable message", description = "Can be used for " + + "unregistering the connector at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + @PreAuthorize("hasPermission(#recipient, 'rw')") + public ResponseEntity sendConnectorUpdateMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final String recipient) { + try { + // Update the config model. + connectorService.updateConfigModel(); + + // Send the connector unavailable message. + final var response = brokerService.unregisterAtBroker(recipient); + final var responseToString = Objects.requireNonNull(response.body()).string(); + return ResponseEntity.ok(responseToString); + } catch (ConfigurationUpdateException exception) { + return ControllerUtils.respondConfigurationUpdateError(exception); + } catch (NullPointerException | IOException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageController.java new file mode 100644 index 000000000..0a35e8c60 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageController.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.utils.ControllerUtils; +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.Objects; + +/** + * Controller for sending ids connector update messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class ConnectorUpdateMessageController { + + /** + * The service for communication with the ids broker. + */ + private final @NonNull IDSBrokerService brokerService; + + /** + * Service for the current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Sending an ids connector update message with the current connector as payload. + * TODO Validate response message and return OK or other status code. + * + * @param recipient The url of the recipient. + * @return The response message or an error. + */ + @PostMapping("/connector/update") + @Operation(summary = "Connector update message", description = "Can be used for registering or " + + "updating the connector at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + @PreAuthorize("hasPermission(#recipient, 'rw')") + public ResponseEntity sendConnectorUpdateMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final String recipient) { + try { + // Update the config model. + connectorService.updateConfigModel(); + + // Send the connector update message. + final var response = brokerService.updateSelfDescriptionAtBroker(recipient); + final var responseToString = Objects.requireNonNull(response.body()).string(); + return ResponseEntity.ok(responseToString); + } catch (ConfigurationUpdateException exception) { + return ControllerUtils.respondConfigurationUpdateError(exception); + } catch (NullPointerException | IOException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/ContractRequestMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/ContractRequestMessageController.java new file mode 100644 index 000000000..ece4769ed --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/ContractRequestMessageController.java @@ -0,0 +1,259 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.persistence.PersistenceException; + +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.InvalidInputException; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.services.EntityPersistenceService; +import io.dataspaceconnector.services.EntityUpdateService; +import io.dataspaceconnector.services.messages.types.ArtifactRequestService; +import io.dataspaceconnector.services.messages.types.ContractAgreementService; +import io.dataspaceconnector.services.messages.types.ContractRequestService; +import io.dataspaceconnector.services.messages.types.DescriptionRequestService; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.usagecontrol.ContractManager; +import io.dataspaceconnector.utils.ControllerUtils; +import io.dataspaceconnector.utils.MessageUtils; +import io.dataspaceconnector.utils.RuleUtils; +import io.dataspaceconnector.view.AgreementViewAssembler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * This controller provides the endpoint for sending a contract request message and starting the + * metadata and data exchange. + */ +@Log4j2 +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class ContractRequestMessageController { + + /** + * Service for contract request message handling. + */ + private final @NonNull ContractRequestService contractReqSvc; + + /** + * Service for artifact request message handling. + */ + private final @NonNull ArtifactRequestService artifactReqSvc; + + /** + * Service for description request message handling. + */ + private final @NonNull DescriptionRequestService descReqSvc; + + /** + * Service for contract agreement message handling. + */ + private final @NonNull ContractAgreementService agreementSvc; + + /** + * Service for updating database entities. + */ + private final @NonNull EntityUpdateService updateService; + + /** + * Assemblers DTOs for agreements. + */ + private final @NonNull AgreementViewAssembler agreementAsm; + + /** + * Used for gaining access to agreements. + */ + private final @NonNull AgreementService agreementService; + + /** + * Service for contract processing. + */ + private final @NonNull ContractManager contractManager; + + /** + * Service for persisting entities. + */ + private final @NonNull EntityPersistenceService persistenceSvc; + + /** + * Starts a contract, metadata, and data exchange with an external connector. + * + * @param recipient The recipient. + * @param resources List of requested resources by IDs. + * @param artifacts List of requested artifacts by IDs. + * @param download Download data directly after successful contract and description request. + * @param ruleList List of rules that should be used within a contract request. + * @return The response entity. + */ + @PostMapping(value = "/contract") + @Operation(summary = "Send ids description request message") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "201", description = "Created"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "417", description = "Expectation failed"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @PreAuthorize("hasPermission(#recipient, 'rw')") + @ResponseBody + public ResponseEntity sendContractRequestMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final URI recipient, + @Parameter(description = "List of ids resource that should be requested.") + @RequestParam(value = "resourceIds") final List resources, + @Parameter(description = "List of ids artifacts that should be requested.") + @RequestParam(value = "artifactIds") final List artifacts, +// @Parameter(description = "Indicates whether the connector should listen on remote " +// + "updates.") @RequestParam(value = "subscribe") final boolean subscribe, + @Parameter(description = "Indicates whether the connector should automatically " + + "download data of an artifact.") + @RequestParam(value = "download") final boolean download, + @Parameter(description = "List of ids rules with an artifact id as target.") + @RequestBody final List ruleList) { + UUID agreementId; + + Map response; + try { + // Validate input for contract request. + RuleUtils.validateRuleTarget(ruleList); + final var request = contractManager.buildContractRequest(ruleList); + + // CONTRACT NEGOTIATION ---------------------------------------------------------------- + // Send and validate contract request/response message. + response = contractReqSvc.sendMessage(recipient, request); + if (!contractReqSvc.validateResponse(response)) { + // If the response is not a contract agreement message, show the response. + final var content = contractReqSvc.getResponseContent(response); + return ControllerUtils.respondWithMessageContent(content); + } + + // Read and process the response message. + final var payload = MessageUtils.extractPayloadFromMultipartMessage(response); + final var agreement = contractManager.validateContractAgreement(payload, request); + + // Send and validate contract agreement/response message. + response = agreementSvc.sendMessage(recipient, agreement); + if (!agreementSvc.validateResponse(response)) { + // If the response is not a notification message, show the response. + final var content = agreementSvc.getResponseContent(response); + return ControllerUtils.respondWithMessageContent(content); + } + + // Save contract agreement to database. + agreementId = persistenceSvc.saveContractAgreement(agreement); + if (log.isDebugEnabled()) { + log.debug("Policy negotiation success. Saved agreement. [agreemendId=({})].", + agreementId); + } + + // DESCRIPTION REQUESTS ---------------------------------------------------------------- + // Iterate over list of resource ids to send description request messages for each. + for (final var resource : resources) { + // Send and validate description request/response message. + response = descReqSvc.sendMessage(recipient, resource); + if (!descReqSvc.validateResponse(response)) { + // If the response is not a description response message, show the response. + final var content = descReqSvc.getResponseContent(response); + return ControllerUtils.respondWithMessageContent(content); + } + + // Read and process the response message. Save resource, recipient, and agreement + // id to database. + persistenceSvc.saveMetadata(response, artifacts, download, recipient); + } + + updateService.linkArtifactToAgreement(artifacts, agreementId); + + // ARTIFACT REQUESTS ------------------------------------------------------------------- + // Download data depending on user input. + if (download) { + // Iterate over list of resource ids to send artifact request messages for each. + for (final var artifact : artifacts) { + // Send and validate artifact request/response message. + final var transferContract = agreement.getId(); + response = artifactReqSvc.sendMessage(recipient, artifact, transferContract); + if (!artifactReqSvc.validateResponse(response)) { + // If the response is not an artifact response message, show the response. + // Ignore when data could not be downloaded, because the artifact request + // can be triggered later again. + final var content = artifactReqSvc.getResponseContent(response); + if (log.isDebugEnabled()) { + log.debug("Data could not be loaded. [content=({})]", content); + } + } + + // Read and process the response message. + try { + persistenceSvc.saveData(response, artifact); + } catch (ResourceNotFoundException | MessageResponseException exception) { + // Ignore that the data saving failed. Another try can take place later. + if (log.isWarnEnabled()) { + log.warn("Could not save data for artifact." + + "[artifact=({}), exception=({})]", + artifact, exception.getMessage()); + } + } + } + } + } catch (InvalidInputException exception) { + return ControllerUtils.respondInvalidInput(exception); + } catch (ConstraintViolationException exception) { + return ControllerUtils.respondFailedToBuildContractRequest(exception); + } catch (PersistenceException exception) { + return ControllerUtils.respondFailedToStoreEntity(exception); + } catch (MessageException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } catch (MessageResponseException | IllegalArgumentException | ContractException e) { + return ControllerUtils.respondReceivedInvalidResponse(e); + } + + // Return response entity containing the locations of the contract agreement, the + // downloaded resources, and the downloaded data. + + final var entity = agreementAsm.toModel(agreementService.get(agreementId)); + + final var headers = new HttpHeaders(); + headers.setLocation(entity.getLink("self").get().toUri()); + + return new ResponseEntity<>(entity, headers, HttpStatus.CREATED); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageController.java new file mode 100644 index 000000000..5251c6b12 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageController.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.messages.types.DescriptionRequestService; +import io.dataspaceconnector.utils.ControllerUtils; +import io.dataspaceconnector.utils.MessageUtils; +import io.dataspaceconnector.utils.Utils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +/** + * Controller for sending description request messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class DescriptionRequestMessageController { + + /** + * Service for message handling. + */ + private final @NonNull DescriptionRequestService messageService; + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Requests metadata from an external connector by building an DescriptionRequestMessage. + * + * @param recipient The target connector url. + * @param elementId The requested element id. + * @return The response entity. + */ + @PostMapping("/description") + @Operation(summary = "Send ids description request message") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "417", description = "Expectation failed"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @PreAuthorize("hasPermission(#recipient, 'rw')") + @ResponseBody + public ResponseEntity sendDescriptionRequestMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final URI recipient, + @Parameter(description = "The id of the requested resource.") + @RequestParam(value = "elementId", required = false) final URI elementId) { + String payload = null; + try { + // Send and validate description request/response message. + final var response = messageService.sendMessage(recipient, elementId); + final var valid = messageService.validateResponse(response); + if (!valid) { + // If the response is not a description response message, show the response. + final var content = messageService.getResponseContent(response); + return ControllerUtils.respondWithMessageContent(content); + } + + // Read and process the response message. + payload = MessageUtils.extractPayloadFromMultipartMessage(response); + if (!Utils.isEmptyOrNull(elementId)) { + return new ResponseEntity<>(payload, HttpStatus.OK); + } else { + // Get payload as component. + final var component = + deserializationService.getInfrastructureComponent(payload); + return ResponseEntity.ok(component.toRdf()); + } + } catch (MessageException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } catch (MessageResponseException exception) { + return ControllerUtils.respondReceivedInvalidResponse(exception); + } catch (IllegalArgumentException exception) { + // If the response is not of type base connector. + return new ResponseEntity<>(payload, HttpStatus.OK); + } catch (Exception exception) { + return ControllerUtils.respondGlobalException(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/QueryMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/QueryMessageController.java new file mode 100644 index 000000000..39f8aa32f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/QueryMessageController.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; +import java.util.Objects; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import io.dataspaceconnector.utils.ControllerUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for sending ids query messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class QueryMessageController { + + /** + * The service for communication with the ids broker. + */ + private final @NonNull IDSBrokerService brokerService; + + /** + * Sending an ids query message with a query message as payload. + * TODO Validate response message and return OK or other status code. + * + * @param recipient The url of the recipient. + * @param query The query statement. + * @return The response message or an error. + */ + @PostMapping("/query") + @Operation(summary = "Query message", description = "Can be used for querying an " + + "IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + @PreAuthorize("hasPermission(#recipient, 'rw')") + public ResponseEntity sendConnectorUpdateMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final URI recipient, + @Schema(description = "Database query (SparQL)", required = true, + example = "SELECT ?subject ?predicate ?object\n" + + "FROM \n" + + "WHERE {\n" + + " ?subject ?predicate ?object\n" + + "};") @RequestBody final String query) { + try { + // Send the resource update message. + final var response = brokerService.queryBroker(recipient.toString(), query, + null, null, null); + final var responseToString = Objects.requireNonNull(response.body()).string(); + return ResponseEntity.ok(responseToString); + } catch (IOException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageController.java new file mode 100644 index 000000000..e1027baad --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageController.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; +import java.util.Objects; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.utils.ControllerUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for sending ids resource unavailable messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class ResourceUnavailableMessageController { + + /** + * The service for communication with the ids broker. + */ + private final @NonNull IDSBrokerService brokerService; + + /** + * Service for current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Sending an ids resource unavailable message with a resource as payload. + * TODO Validate response message and return OK or other status code. + * + * @param recipient The url of the recipient. + * @param resourceId The resource id. + * @return The response message or an error. + */ + @PostMapping("/resource/unavailable") + @Operation(summary = "Resource unavailable message", description = "Can be used for " + + "unregistering a resource at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + @PreAuthorize("hasPermission(#recipient, 'rw')") + public ResponseEntity sendConnectorUpdateMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final String recipient, + @Parameter(description = "The resource id.", required = true) + @RequestParam(value = "resourceId") final URI resourceId) { + try { + final var resource = connectorService.getOfferedResourceById(resourceId); + if (resource.isEmpty()) { + return ControllerUtils.respondResourceNotFound(resourceId); + } + + // Send the resource unavailable message. + final var response = brokerService.removeResourceFromBroker(recipient, resource.get()); + final var responseToString = Objects.requireNonNull(response.body()).string(); + return ResponseEntity.ok(responseToString); + } catch (NullPointerException | IOException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageController.java b/src/main/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageController.java new file mode 100644 index 000000000..e1e6560a4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageController.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; +import java.util.Objects; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.utils.ControllerUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for sending ids resource update messages. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ids") +@Tag(name = "IDS Messages", description = "Endpoints for invoke sending IDS messages") +public class ResourceUpdateMessageController { + + /** + * The service for communication with the ids broker. + */ + private final @NonNull IDSBrokerService brokerService; + + /** + * Service for ids resources. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Sending an ids resource update message with a resource as payload. + * TODO Validate response message and return OK or other status code. + * + * @param recipient The url of the recipient. + * @param resourceId The resource id. + * @return The response message or an error. + */ + @PostMapping("/resource/update") + @Operation(summary = "Resource update message", description = "Can be used for registering or " + + "updating a resource at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @ResponseBody + @PreAuthorize("hasPermission(#recipient, 'rw')") + public ResponseEntity sendConnectorUpdateMessage( + @Parameter(description = "The recipient url.", required = true) + @RequestParam("recipient") final String recipient, + @Parameter(description = "The resource id.", required = true) + @RequestParam(value = "resourceId") final URI resourceId) { + try { + final var resource = connectorService.getOfferedResourceById(resourceId); + if (resource.isEmpty()) { + return ControllerUtils.respondResourceNotFound(resourceId); + } + + // Send the resource update message. + final var response = brokerService.updateResourceAtBroker(recipient, resource.get()); + final var responseToString = Objects.requireNonNull(response.body()).string(); + return ResponseEntity.ok(responseToString); + } catch (NullPointerException | IOException exception) { + return ControllerUtils.respondIdsMessageFailed(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/messages/package-info.java b/src/main/java/io/dataspaceconnector/controller/messages/package-info.java new file mode 100644 index 000000000..42710f976 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/messages/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Groups all controllers exposing endpoints for sending IDS related messages. + */ +package io.dataspaceconnector.controller.messages; diff --git a/src/main/java/io/dataspaceconnector/controller/package-info.java b/src/main/java/io/dataspaceconnector/controller/package-info.java new file mode 100644 index 000000000..624309075 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Groups all controllers exposing endpoints for interacting with + * the Dataspace Connector. + */ +package io.dataspaceconnector.controller; diff --git a/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceChildController.java b/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceChildController.java new file mode 100644 index 000000000..f30d36297 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceChildController.java @@ -0,0 +1,181 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.net.URI; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.validation.Valid; + +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.services.resources.RelationService; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.UUIDUtils; +import io.dataspaceconnector.utils.Utils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.GenericTypeResolver; +import org.springframework.data.domain.Page; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Offers REST-Api Endpoints for modifying relations between REST resources. + * + * @param The service type for handling the relations logic. + * @param The type of the entity operated on. + * @param The type of the view model produces. + */ +public class BaseResourceChildController, + T extends AbstractEntity, V extends RepresentationModel> { + /** + * The linker between two resources. + **/ + @Autowired + private S linker; + + /** + * The assembler for converting entites to views. + */ + @Autowired + private RepresentationModelAssembler assembler; + + /** + * The assembler for creating list of views. + */ + @Autowired + private PagedResourcesAssembler pagedResourcesAssembler; + + /** + * The type of the service. + */ + private final Class resourceType; + + /** + * Default constructor. + */ + protected BaseResourceChildController() { + final var resolved = GenericTypeResolver + .resolveTypeArguments(getClass(), BaseResourceChildController.class); + resourceType = (Class) resolved[2]; + } + + /** + * Get all resources of the same type linked to the passed resource. + * Endpoint for GET requests. + * @param ownerId The id of the owning resource. + * @param page The page index. + * @param size The page size. + * @return The children of the resource. + * @throws IllegalArgumentException if the ownerId is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the ownerId is not known. + */ + @RequestMapping(method = RequestMethod.GET) + @Operation(summary = "Get all children of a base resource with pagination") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public ResponseEntity> getResource( + @Valid @PathVariable(name = "id") final UUID ownerId, + @RequestParam(required = false, defaultValue = "0") final Integer page, + @RequestParam(required = false, defaultValue = "30") final Integer size) { + final var pageable = Utils.toPageRequest(page, size); + final var entities = linker.get(ownerId, pageable); + + PagedModel model; + if (entities.hasContent()) { + model = pagedResourcesAssembler.toModel((Page) entities, assembler); + } else { + model = (PagedModel) pagedResourcesAssembler.toEmptyModel(entities, resourceType); + } + + return ResponseEntity.ok(model); + } + + /** + * Add resources as children to a resource. Endpoint for POST requests. + * + * @param ownerId The owning resource. + * @param resources The children to be added. + * @return Response with code 200 (Ok) and the new children's list. + */ + @PostMapping + @Operation(summary = "Add a list of children to a base resource") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public HttpEntity> addResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + Utils.requireNonNull(resources, ErrorMessages.LIST_NULL); + + linker.add(ownerId, toSet(resources)); + // Send back the list of children after modification. + // See https://tools.ietf.org/html/rfc7231#section-4.3.3 and + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + return this.getResource(ownerId, null, null); + } + + /** + * Replace the children of a resource. Endpoint for PUT requests. + * + * @param ownerId The id of the resource which children should be replaced. + * @param resources The resources that should be added as children. + * @return Response with code 204 (No_Content). + */ + @PutMapping + @Operation(summary = "Replace the children of a base resource") + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public HttpEntity replaceResources(@Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + linker.replace(ownerId, toSet(resources)); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + /** + * Remove a list of children of a resource. Endpoint for DELETE requests. + * + * @param ownerId The id of the resource which children should be removed. + * @param resources The list of children to be removed. + * @return Response with code 204 (No_Content). + */ + @DeleteMapping + @Operation(summary = "Remove a list of children from a base resource") + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public HttpEntity removeResources(@Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + linker.remove(ownerId, toSet(resources)); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + private static Set toSet(final List uris) { + return uris.parallelStream().map(UUIDUtils::uuidFromUri).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceController.java b/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceController.java new file mode 100644 index 000000000..cff9904b8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/BaseResourceController.java @@ -0,0 +1,208 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.util.UUID; +import javax.validation.Valid; + +import io.dataspaceconnector.model.AbstractDescription; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.services.resources.BaseEntityService; +import io.dataspaceconnector.utils.Utils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.GenericTypeResolver; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Offers REST-Api endpoints for REST resource handling. + * + * @param The type of the resource. + * @param The type of the resource description expected to be passed with REST calls. + * @param The type of the view produces by this controller. + * @param The underlying service for handling the resource logic. + */ +public class BaseResourceController, V + extends RepresentationModel, S + extends BaseEntityService> { + /** + * The service for the resource logic. + **/ + @Autowired + private S service; + + /** + * The assembler for creating a view from an entity. + */ + @Autowired + private RepresentationModelAssembler assembler; + + /** + * The assembler for creating list of views. + */ + @Autowired + private PagedResourcesAssembler pagedResourcesAssembler; + + /** + * The type of the entity used for creating empty pages. + */ + private final Class resourceType; + + /** + * Default constructor. + */ + protected BaseResourceController() { + final var resolved = + GenericTypeResolver.resolveTypeArguments(getClass(), BaseResourceController.class); + resourceType = (Class) resolved[2]; + } + + /** + * Creates a new resource. Endpoint for POST requests. + * @param desc The resource description. + * @return Response with code 201 (Created). + * @throws IllegalArgumentException if the description is null. + */ + @PostMapping + @Operation(summary = "Create a base resource") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Created")}) + public ResponseEntity create(@RequestBody final D desc) { + final var obj = service.create(desc); + final var entity = assembler.toModel(obj); + + final var headers = new HttpHeaders(); + headers.setLocation(entity.getLink("self").get().toUri()); + + return new ResponseEntity<>(entity, headers, HttpStatus.CREATED); + } + + /** + * Get a list of all resources endpoints of this type. + * Endpoint for GET requests. + * @param page The page index. + * @param size The page size. + * @return Response with code 200 (Ok) and the list of all endpoints of this resource type. + */ + @RequestMapping(method = RequestMethod.GET) + @Operation(summary = "Get a list of base resources with pagination") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public ResponseEntity> getAll( + @RequestParam(required = false, defaultValue = "0") final Integer page, + @RequestParam(required = false, defaultValue = "30") final Integer size) { + final var pageable = Utils.toPageRequest(page, size); + final var entities = service.getAll(pageable); + PagedModel model; + if (entities.hasContent()) { + model = pagedResourcesAssembler.toModel(entities, assembler); + } else { + model = (PagedModel) pagedResourcesAssembler.toEmptyModel(entities, resourceType); + } + + return ResponseEntity.ok(model); + } + + /** + * Get a resource. Endpoint for GET requests. + * @param resourceId The id of the resource. + * @return The resource. + * @throws IllegalArgumentException if the resourceId is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the resourceId is unknown. + */ + @RequestMapping(value = "{id}", method = RequestMethod.GET) + @Operation(summary = "Get a base resource by id") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public ResponseEntity get(@Valid @PathVariable(name = "id") final UUID resourceId) { + final var view = assembler.toModel(service.get(resourceId)); + return ResponseEntity.ok(view); + } + + /** + * Update a resource details. Endpoint for PUT requests. + * + * @param resourceId The id of the resource to be updated. + * @param desc The new description of the resource. + * @return Response with code (No_Content) when the resource has been updated or response with + * code (201) if the resource has been updated and been moved to a new endpoint. + * @throws IllegalArgumentException if the any of the parameters is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the resourceId is unknown. + */ + @PutMapping(value = "{id}") + @Operation(summary = "Update a base resource by id") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Created"), + @ApiResponse(responseCode = "204", description = "No Content")}) + public ResponseEntity update( + @Valid @PathVariable(name = "id") final UUID resourceId, @RequestBody final D desc) { + final var resource = service.update(resourceId, desc); + + ResponseEntity response; + if (resource.getId().equals(resourceId)) { + // The resource was not moved + response = new ResponseEntity<>(HttpStatus.NO_CONTENT); + } else { + // The resource has been moved + final var entity = assembler.toModel(resource); + final var headers = new HttpHeaders(); + headers.setLocation(entity.getLink("self").get().toUri()); + + response = new ResponseEntity<>(entity, headers, HttpStatus.CREATED); + } + + return response; + } + + /** + * Delete a resource. Endpoint for DELETE requests. + * @param resourceId The id of the resource to be deleted. + * @return Response with code 204 (No_Content). + * @throws IllegalArgumentException if the resourceId is null. + */ + @DeleteMapping(value = "{id}") + @Operation(summary = "Delete a base resource by id") + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content")}) + public ResponseEntity delete(@Valid @PathVariable(name = "id") final UUID resourceId) { + service.delete(resourceId); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + /** + * Get the service responsible for the resource's logic handling. + * + * @return The service. + */ + protected S getService() { + return service; + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/resources/RelationControllers.java b/src/main/java/io/dataspaceconnector/controller/resources/RelationControllers.java new file mode 100644 index 000000000..5878fd74d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/RelationControllers.java @@ -0,0 +1,298 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import javax.validation.Valid; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.services.resources.AbstractCatalogResourceLinker; +import io.dataspaceconnector.services.resources.AbstractResourceContractLinker; +import io.dataspaceconnector.services.resources.AbstractResourceRepresentationLinker; +import io.dataspaceconnector.services.resources.RelationServices; +import io.dataspaceconnector.view.AgreementView; +import io.dataspaceconnector.view.ArtifactView; +import io.dataspaceconnector.view.CatalogView; +import io.dataspaceconnector.view.ContractRuleView; +import io.dataspaceconnector.view.ContractView; +import io.dataspaceconnector.view.OfferedResourceView; +import io.dataspaceconnector.view.RepresentationView; +import io.dataspaceconnector.view.RequestedResourceView; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.hateoas.PagedModel; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class contains all implementations of the {@link BaseResourceChildController}. + */ +public final class RelationControllers { + + /** + * Offers the endpoints for managing the relations between rules and contracts. + */ + @RestController + @RequestMapping("/api/rules/{id}/contracts") + @Tag(name = "Rules", description = "Endpoints for linking rules to contracts") + public static class RulesToContracts extends BaseResourceChildController< + RelationServices.RuleContractLinker, Contract, ContractView> { + } + + /** + * Offers the endpoints for managing the relations between artifacts and representations. + */ + @RestController + @RequestMapping("/api/artifacts/{id}/representations") + @Tag(name = "Artifacts", description = "Endpoints for linking artifacts to representations") + public static class ArtifactsToRepresentations extends BaseResourceChildController< + RelationServices.ArtifactRepresentationLinker, Representation, RepresentationView> { + } + + /** + * Offers the endpoints for managing the relations between representations and offered + * resources. + */ + @RestController + @RequestMapping("/api/representations/{id}/offers") + @Tag(name = "Representations", description = "Endpoints for linking representations to " + + "offered resources") + public static class RepresentationsToOfferedResources extends BaseResourceChildController< + RelationServices.RepresentationOfferedResourceLinker, OfferedResource, + OfferedResourceView> { + } + + /** + * Offers the endpoints for managing the relations between representations and requested + * resources. + */ + @RestController + @RequestMapping("/api/representations/{id}/requests") + @Tag(name = "Representations", description = "Endpoints for linking representations to " + + "requested resources") + public static class RepresentationsToRequestedResources extends BaseResourceChildController< + RelationServices.RepresentationOfferedResourceLinker, RequestedResource, + RequestedResourceView> { + } + + /** + * Offers the endpoints for managing the relations between offered resources and catalogs. + */ + @RestController + @RequestMapping("/api/offers/{id}/catalogs") + @Tag(name = "Resources", description = "Endpoints for linking offered resources to catalogs") + public static class OfferedResourcesToCatalogs extends BaseResourceChildController< + RelationServices.OfferedResourceCatalogLinker, Catalog, CatalogView> { + } + + /** + * Offers the endpoints for managing the relations between requested resources and catalogs. + */ + @RestController + @RequestMapping("/api/requests/{id}/catalogs") + @Tag(name = "Resources", description = "Endpoints for linking requested resources to catalogs") + public static class RequestedResourcesToCatalogs extends BaseResourceChildController< + RelationServices.RequestedResourceCatalogLinker, Catalog, CatalogView> { + } + + /** + * Offers the endpoints for managing the relations between contracts and offered resources. + */ + @RestController + @RequestMapping("/api/contracts/{id}/offers") + @Tag(name = "Contracts", description = "Endpoints for linking contracts to offers") + public static class ContractsToOfferedResources extends BaseResourceChildController< + RelationServices.ContractOfferedResourceLinker, OfferedResource, + OfferedResourceView> { + } + + /** + * Offers the endpoints for managing the relations between contracts and requested resources. + */ + @RestController + @RequestMapping("/api/contracts/{id}/requests") + @Tag(name = "Contracts", description = "Endpoints for linking contracts to requests") + public static class ContractsToRequestedResources extends BaseResourceChildController< + RelationServices.ContractRequestedResourceLinker, RequestedResource, + RequestedResourceView> { + } + + /** + * Offers the endpoints for managing the relations between artifacts and agreements. + */ + @RestController + @RequestMapping("/api/artifacts/{id}/agreements") + @Tag(name = "Artifacts", description = "Endpoints for linking artifacts to agreements") + public static class ArtifactsToAgreements extends BaseResourceChildController< + RelationServices.ArtifactAgreementLinker, Agreement, AgreementView> { + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final HttpEntity> addResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public final HttpEntity replaceResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public final HttpEntity removeResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + } + + /** + * Offers the endpoints for managing the relations between agreements and artifacts. + */ + @RestController + @RequestMapping("/api/agreements/{id}/artifacts") + @Tag(name = "Agreements", description = "Endpoints for linking agreements to artifacts") + public static class AgreementsToArtifacts extends BaseResourceChildController< + RelationServices.AgreementArtifactLinker, Artifact, ArtifactView> { + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final HttpEntity> addResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public final HttpEntity replaceResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No content")}) + public final HttpEntity removeResources( + @Valid @PathVariable(name = "id") final UUID ownerId, + @Valid @RequestBody final List resources) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + } + + /** + * Offers the endpoints for managing the relations between representations and artifacts. + */ + @RestController + @RequestMapping("/api/representations/{id}/artifacts") + @Tag(name = "Representations", description = "Endpoints for linking artifacts to " + + "representations") + public static class RepresentationsToArtifacts + extends BaseResourceChildController { + } + + /** + * Offers the endpoints for managing the relations between contracts and rules. + */ + @RestController + @RequestMapping("/api/contracts/{id}/rules") + @Tag(name = "Contracts", description = "Endpoints for linking rules to contracts") + public static class ContractsToRules extends BaseResourceChildController< + RelationServices.ContractRuleLinker, ContractRule, ContractRuleView> { + } + + /** + * Offers the endpoints for managing the relations between catalogs and offered resources. + */ + @RestController + @RequestMapping("/api/catalogs/{id}/offers") + @Tag(name = "Catalogs", description = "Endpoints for linking offered resources to catalogs") + public static class CatalogsToOfferedResources extends BaseResourceChildController< + AbstractCatalogResourceLinker, OfferedResource, OfferedResourceView> { + } + + /** + * Offers the endpoints for managing the relations between offered resources and contracts. + */ + @RestController + @RequestMapping("/api/offers/{id}/contracts") + @Tag(name = "Resources", description = "Endpoints for linking contracts to resources") + public static class OfferedResourcesToContracts + extends BaseResourceChildController, + Contract, ContractView> { + } + + /** + * Offers the endpoints for managing the relations between offered resources and + * representations. + */ + @RestController + @RequestMapping("/api/offers/{id}/representations") + @Tag(name = "Resources", description = "Endpoints for linking representations to resources") + public static class OfferedResourcesToRepresentations + extends BaseResourceChildController, Representation, RepresentationView> { + } + + /** + * Offers the endpoints for managing the relations between requested resources and contracts. + */ + @RestController + @RequestMapping("/api/requests/{id}/contracts") + @Tag(name = "Resources", description = "Endpoints for linking contracts to resources") + public static class RequestedResourcesToContracts + extends BaseResourceChildController, + Contract, ContractView> { + } + + /** + * Offers the endpoints for managing the relations between requested resources and + * representations. + */ + @RestController + @RequestMapping("/api/requests/{id}/representations") + @Tag(name = "Resources", description = "Endpoints for linking representations to resources") + public static class RequestedResourcesToRepresentations + extends BaseResourceChildController, Representation, RepresentationView> { + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/resources/ResourceControllers.java b/src/main/java/io/dataspaceconnector/controller/resources/ResourceControllers.java new file mode 100644 index 000000000..15e9fad6d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/ResourceControllers.java @@ -0,0 +1,320 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.util.Map; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.AgreementDesc; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.services.BlockingArtifactReceiver; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.CatalogService; +import io.dataspaceconnector.services.resources.ContractService; +import io.dataspaceconnector.services.resources.RepresentationService; +import io.dataspaceconnector.services.resources.ResourceService; +import io.dataspaceconnector.services.resources.RetrievalInformation; +import io.dataspaceconnector.services.resources.RuleService; +import io.dataspaceconnector.services.usagecontrol.DataAccessVerifier; +import io.dataspaceconnector.utils.ValidationUtils; +import io.dataspaceconnector.view.AgreementView; +import io.dataspaceconnector.view.ArtifactView; +import io.dataspaceconnector.view.CatalogView; +import io.dataspaceconnector.view.ContractRuleView; +import io.dataspaceconnector.view.ContractView; +import io.dataspaceconnector.view.OfferedResourceView; +import io.dataspaceconnector.view.RepresentationView; +import io.dataspaceconnector.view.RequestedResourceView; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +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.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +/** + * This class contains all implementations of the {@link BaseResourceController}. + */ +public final class ResourceControllers { + + /** + * Offers the endpoints for managing catalogs. + */ + @RestController + @RequestMapping("/api/catalogs") + @Tag(name = "Catalogs", description = "Endpoints for CRUD operations on catalogs") + public static class CatalogController + extends BaseResourceController { + } + + /** + * Offers the endpoints for managing rules. + */ + @RestController + @RequestMapping("/api/rules") + @Tag(name = "Rules", description = "Endpoints for CRUD operations on rules") + public static class RuleController extends BaseResourceController { + } + + /** + * Offers the endpoints for managing representations. + */ + @RestController + @RequestMapping("/api/representations") + @Tag(name = "Representations", description = "Endpoints for CRUD operations on representations") + public static class RepresentationController extends BaseResourceController { + } + + /** + * Offers the endpoints for managing contracts. + */ + @RestController + @RequestMapping("/api/contracts") + @Tag(name = "Contracts", description = "Endpoints for CRUD operations on contracts") + public static class ContractController + extends BaseResourceController { + } + + /** + * Offers the endpoints for managing offered resources. + */ + @RestController + @RequestMapping("/api/offers") + @Tag(name = "Resources", description = "Endpoints for CRUD operations on offered resources") + public static class OfferedResourceController + extends BaseResourceController> { + } + + /** + * Offers the endpoints for managing requested resources. + */ + @RestController + @RequestMapping("/api/requests") + @Tag(name = "Resources", description = "Endpoints for CRUD operations on requested resources") + public static class RequestedResourceController + extends BaseResourceController> { + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final ResponseEntity create( + final RequestedResourceDesc desc) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + } + + /** + * Offers the endpoints for managing agreements. + */ + @RestController + @RequestMapping("/api/agreements") + @Tag(name = "Agreements", description = "Endpoints for contract/policy handling") + public static class AgreementController extends BaseResourceController { + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final ResponseEntity create(final AgreementDesc desc) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final ResponseEntity update(@Valid final UUID resourceId, + final AgreementDesc desc) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + + @Override + @Hidden + @ApiResponses(value = {@ApiResponse(responseCode = "405", description = "Not allowed")}) + public final ResponseEntity delete(@Valid final UUID resourceId) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); + } + } + + /** + * Offers the endpoints for managing artifacts. + */ + @RestController + @RequestMapping("/api/artifacts") + @Tag(name = "Artifacts", description = "Endpoints for CRUD operations on artifacts") + @RequiredArgsConstructor + public static class ArtifactController + extends BaseResourceController { + + /** + * The service managing artifacts. + */ + private final @NonNull ArtifactService artifactSvc; + + /** + * The receiver for getting data from a remote source. + */ + private final @NonNull BlockingArtifactReceiver dataReceiver; + + /** + * The verifier for the data access. + */ + private final @NonNull + DataAccessVerifier accessVerifier; + + /** + * Returns data from the local database or a remote data source. In case of a remote data + * source, all headers and query parameters included in this request will be used for the + * request to the backend. + * + * @param artifactId Artifact id. + * @param download If the data should be forcefully downloaded. + * @param agreementUri The agreement which should be used for access control. + * @param params All request parameters. + * @param headers All request headers. + * @param request The current http request. + * @return The data object. + */ + @GetMapping("{id}/data/**") + @Operation(summary = "Get data by artifact id with query input") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public ResponseEntity getData( + @Valid @PathVariable(name = "id") final UUID artifactId, + @RequestParam(required = false) final Boolean download, + @RequestParam(required = false) final URI agreementUri, + @RequestParam final Map params, + @RequestHeader final Map headers, + final HttpServletRequest request) { + headers.remove("authorization"); + headers.remove("host"); + + final var queryInput = new QueryInput(); + queryInput.setParams(params); + queryInput.setHeaders(headers); + + final var searchString = request.getContextPath() + "/data"; + final var optional = request.getRequestURI().substring( + request.getRequestURI().indexOf(searchString) + searchString.length()); + + if (!optional.isBlank()) { + queryInput.setOptional(optional); + } + + /* + If no agreement information has been passed the connector needs + to check if the data access is restricted by the usage control. + */ + // TODO: Check what happens when this connector is the provider and one of its provided + // agreements is passed. + final var data = (agreementUri == null) + ? artifactSvc.getData(accessVerifier, dataReceiver, artifactId, queryInput) + : artifactSvc.getData(accessVerifier, dataReceiver, artifactId, + new RetrievalInformation(agreementUri, download, + queryInput)); + + StreamingResponseBody body = outputStream -> { + final int blockSize = 1024; + int numBytesToWrite; + var buffer = new byte[blockSize]; + while ((numBytesToWrite = data.read(buffer, 0, buffer.length)) != -1) { + outputStream.write(buffer, 0, numBytesToWrite); + } + + data.close(); + }; + + final var outputHeader = new HttpHeaders(); + outputHeader.set("Content-Disposition", "attachment;filename=" + artifactId.toString()); + + return ResponseEntity.ok() + .headers(outputHeader) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(body); + } + + /** + * Returns data from the local database or a remote data source. In case of a remote data + * source, the headers, query parameters and path variables from the request body will be + * used when fetching the data. + * + * @param artifactId Artifact id. + * @param queryInput Query input containing headers, query parameters, and path variables. + * @return The data object. + */ + @PostMapping("{id}/data") + @Operation(summary = "Get data by artifact id with query input") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Ok")}) + public ResponseEntity getData( + @Valid @PathVariable(name = "id") final UUID artifactId, + @RequestBody(required = false) final QueryInput queryInput) { + ValidationUtils.validateQueryInput(queryInput); + return ResponseEntity.ok(artifactSvc.getData(accessVerifier, dataReceiver, artifactId, + queryInput)); + } + + /** + * Replace the data of an artifact. + * + * @param artifactId The artifact whose data should be replaced. + * @param inputStream The new data. + * @return Http Status ok. + */ + @PutMapping(value = "{id}/data", consumes = "*/*") + public ResponseEntity putData( + @Valid @PathVariable(name = "id") final UUID artifactId, + @RequestBody final byte[] inputStream) { + artifactSvc.setData(artifactId, new ByteArrayInputStream(inputStream)); + return ResponseEntity.ok().build(); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/resources/ResourceNotFoundExceptionHandler.java b/src/main/java/io/dataspaceconnector/controller/resources/ResourceNotFoundExceptionHandler.java new file mode 100644 index 000000000..89089bef4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/ResourceNotFoundExceptionHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import lombok.extern.log4j.Log4j2; +import net.minidev.json.JSONObject; + +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * Controller for handling {@link ResourceNotFoundException}. + */ +@ControllerAdvice +@Log4j2 +@Order(1) +public final class ResourceNotFoundExceptionHandler { + /** + * Handle {@link ResourceNotFoundException}. + * + * @param exception The thrown exception. + * @return Response entity with code 404. + */ + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFoundException( + final ResourceNotFoundException exception) { + if (log.isDebugEnabled()) { + log.debug("Resource not found. [exception=({})]", exception == null ? "" + : exception.getMessage(), exception); + } + + final var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + final var body = new JSONObject(); + body.put("message", "Resource not found."); + + return new ResponseEntity<>(body, headers, HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/io/dataspaceconnector/controller/resources/package-info.java b/src/main/java/io/dataspaceconnector/controller/resources/package-info.java new file mode 100644 index 000000000..7c3f468b5 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/controller/resources/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Groups all controllers for handling the Dataspace Connector's resources. + */ +package io.dataspaceconnector.controller.resources; diff --git a/src/main/java/io/dataspaceconnector/exceptions/ContractException.java b/src/main/java/io/dataspaceconnector/exceptions/ContractException.java new file mode 100644 index 000000000..43f5fc3ec --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/ContractException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem regarding the contract occurred. + */ +public class ContractException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a ContractException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public ContractException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/InvalidInputException.java b/src/main/java/io/dataspaceconnector/exceptions/InvalidInputException.java new file mode 100644 index 000000000..119169a1f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/InvalidInputException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem regarding a message occurred. + */ +public class InvalidInputException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a InvalidInputException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public InvalidInputException(final String msg) { + super(msg); + } + + /** + * Construct a InvalidInputException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public InvalidInputException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/InvalidResourceException.java b/src/main/java/io/dataspaceconnector/exceptions/InvalidResourceException.java new file mode 100644 index 000000000..4a27bb93c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/InvalidResourceException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that that a problem with the resource composition occurred. + */ +public class InvalidResourceException extends ResourceException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct an InvalidResourceException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public InvalidResourceException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/MessageBuilderException.java b/src/main/java/io/dataspaceconnector/exceptions/MessageBuilderException.java new file mode 100644 index 000000000..6ff03c61b --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/MessageBuilderException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that the message could not be build. + */ +public class MessageBuilderException extends MessageResponseException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageBuilderException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageBuilderException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/MessageEmptyException.java b/src/main/java/io/dataspaceconnector/exceptions/MessageEmptyException.java new file mode 100644 index 000000000..f6f8627b6 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/MessageEmptyException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that the message could not be build. + */ +public class MessageEmptyException extends MessageRequestException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageEmptyException with the specified detail message. + * + * @param msg The detail message. + */ + public MessageEmptyException(final String msg) { + super(msg); + } + + /** + * Construct a MessageEmptyException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageEmptyException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/MessageException.java b/src/main/java/io/dataspaceconnector/exceptions/MessageException.java new file mode 100644 index 000000000..c00b11ee5 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/MessageException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem regarding a message occurred. + */ +public class MessageException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/MessageRequestException.java b/src/main/java/io/dataspaceconnector/exceptions/MessageRequestException.java new file mode 100644 index 000000000..edc50bb33 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/MessageRequestException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem with a message request occurred. + */ +public class MessageRequestException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageRequestException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public MessageRequestException(final String msg) { + super(msg); + } + + /** + * Construct a MessageRequestException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageRequestException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/MessageResponseException.java b/src/main/java/io/dataspaceconnector/exceptions/MessageResponseException.java new file mode 100644 index 000000000..a3a222c41 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/MessageResponseException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem with a message response occurred. + */ +public class MessageResponseException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageResponseException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public MessageResponseException(final String msg) { + super(msg); + } + + /** + * Construct a MessageResponseException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageResponseException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/PolicyExecutionException.java b/src/main/java/io/dataspaceconnector/exceptions/PolicyExecutionException.java new file mode 100644 index 000000000..aec64f366 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/PolicyExecutionException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown if a policy action could not be executed. + */ +public class PolicyExecutionException extends RuntimeException { + + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a PolicyExecutionException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public PolicyExecutionException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/PolicyRestrictionException.java b/src/main/java/io/dataspaceconnector/exceptions/PolicyRestrictionException.java new file mode 100644 index 000000000..5e083c8be --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/PolicyRestrictionException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; + +/** + * Thrown if a policy restriction has been detected. + */ +public class PolicyRestrictionException extends RuntimeException { + + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a PolicyRestrictionException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public PolicyRestrictionException(final ErrorMessages msg) { + super(msg.toString()); + } + + /** + * Construct a PolicyRestrictionException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public PolicyRestrictionException(final ErrorMessages msg, final Throwable cause) { + super(msg.toString(), cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/RdfBuilderException.java b/src/main/java/io/dataspaceconnector/exceptions/RdfBuilderException.java new file mode 100644 index 000000000..a54532fd8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/RdfBuilderException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; + +/** + * Thrown to indicate that the rdf string could not be built. + */ +public class RdfBuilderException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a RdfBuilderException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public RdfBuilderException(final ErrorMessages msg) { + super(msg.toString()); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/ResourceException.java b/src/main/java/io/dataspaceconnector/exceptions/ResourceException.java new file mode 100644 index 000000000..56af159b6 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/ResourceException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that this problem regarding a resource occurred. + */ +public class ResourceException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a ResourceException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public ResourceException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/ResourceNotFoundException.java b/src/main/java/io/dataspaceconnector/exceptions/ResourceNotFoundException.java new file mode 100644 index 000000000..72de7abfa --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/ResourceNotFoundException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that the requested resource could not be found. + */ +public class ResourceNotFoundException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a ResourceNotFoundException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public ResourceNotFoundException(final String msg) { + super(msg); + } + + /** + * Construct a ResourceNotFoundException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public ResourceNotFoundException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/SelfLinkCreationException.java b/src/main/java/io/dataspaceconnector/exceptions/SelfLinkCreationException.java new file mode 100644 index 000000000..5965c7fe0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/SelfLinkCreationException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; + +/** + * Thrown to indicate that the self link for an entity could not be created. + */ +public class SelfLinkCreationException extends RuntimeException { + + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a SelfLinkCreationException with the specified detail message. + * + * @param msg The detail message. + */ + public SelfLinkCreationException(final ErrorMessages msg) { + super(msg.toString()); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/UUIDCreationException.java b/src/main/java/io/dataspaceconnector/exceptions/UUIDCreationException.java new file mode 100644 index 000000000..e80cf903c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/UUIDCreationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem occurred while creating an uuid. + */ +public class UUIDCreationException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a UUIDCreationException with the specified detail message. + * + * @param msg The detail message. + */ + public UUIDCreationException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/UUIDFormatException.java b/src/main/java/io/dataspaceconnector/exceptions/UUIDFormatException.java new file mode 100644 index 000000000..ef55e39f8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/UUIDFormatException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that the application has attempted to convert a string to a uuid, but that + * the string does not have the appropriate format. + */ +public class UUIDFormatException extends IllegalArgumentException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a UUIDFormatException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public UUIDFormatException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/UnreachableLineException.java b/src/main/java/io/dataspaceconnector/exceptions/UnreachableLineException.java new file mode 100644 index 000000000..6c21197a3 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/UnreachableLineException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; + +/** + * Thrown to indicate that this line in the code should not have been possible to reach. + */ +public class UnreachableLineException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a UnexpectedMessageType with the specified detail message. + * + * @param msg The detail message. + */ + public UnreachableLineException(final String msg) { + super(msg); + } + + /** + * Construct a UnexpectedMessageType with the specified detail message. + * + * @param msg The detail message. + */ + public UnreachableLineException(final ErrorMessages msg) { + super(msg.toString()); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/UnsupportedPatternException.java b/src/main/java/io/dataspaceconnector/exceptions/UnsupportedPatternException.java new file mode 100644 index 000000000..98e7fa03e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/UnsupportedPatternException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that this pattern is not supported. + */ +public class UnsupportedPatternException extends ContractException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct an UnsupportedPatternException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public UnsupportedPatternException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/VersionNotSupportedException.java b/src/main/java/io/dataspaceconnector/exceptions/VersionNotSupportedException.java new file mode 100644 index 000000000..9377c21c2 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/VersionNotSupportedException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem occurred while creating an uuid. + */ +public class VersionNotSupportedException extends RuntimeException { + /** + * Default serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct a InfoModelVersionNotSupportedException with the specified detail message. + * + * @param msg The detail message. + */ + public VersionNotSupportedException(final String msg) { + super(msg); + } +} diff --git a/src/main/java/io/dataspaceconnector/exceptions/package-info.java b/src/main/java/io/dataspaceconnector/exceptions/package-info.java new file mode 100644 index 000000000..15f01b3d8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/exceptions/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains custom exceptions thrown in the Dataspace Connector. + */ +package io.dataspaceconnector.exceptions; diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTrace.java b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTrace.java new file mode 100644 index 000000000..aebdd230e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTrace.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing; + +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Data; + +/** + * This class stores information about a http connection. + */ +@Data +@JsonInclude(Include.NON_NULL) +public class HttpTrace { + + /** + * Trace id. + */ + private UUID traceId; + + /** + * Time of trace creation. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-ddTHH:mm:ss.SSSZ") + private ZonedDateTime timestamp; + + /** + * Type of http call, eg. POST, GET, etc. Empty for responses. + */ + private String method; + + /** + * The message target url. + */ + private String url; + + /** + * The message body. + */ + private String body; + + /** + * The message header. + */ + private Map headers; + + /** + * The message status. + */ + private int status; + + /** + * The message client. + */ + private String client; + + /** + * The message parameter map. + */ + private Map parameterMap; +} diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java new file mode 100644 index 000000000..fe4bbe3f7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * Handles the processing of HttpTraces. + */ +@Component +@Log4j2 +@RequiredArgsConstructor +public class HttpTraceEventHandler { + + /** + * The global event publisher used for pushing the http traces. + */ + private final @NonNull ApplicationEventPublisher publisher; + + /** + * Processes raised HttpTraceEvents. + * + * @param trace The HttpTrace that needs to be processed + */ + @Async + @EventListener + public void handleHttpTraceEvent(final HttpTrace trace) { + if (log.isInfoEnabled()) { + log.info("{}", trace); + } + } + + /** + * Raise an HttpTraceEvent. + * + * @param trace The http trace that others should be notified about. + */ + public void sendHttpTraceEvent(final HttpTrace trace) { + if (trace != null) { + publisher.publishEvent(trace); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilter.java b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilter.java new file mode 100644 index 000000000..8186dfed5 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilter.java @@ -0,0 +1,152 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.filter.httptracing.internal.RequestWrapper; +import io.dataspaceconnector.utils.UUIDUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; + +/** + * Use this class to log all incoming and outgoing http traffic. + */ +@Component +@Order(1) +@Log4j2 +@ConditionalOnProperty(name = "httptrace.enabled") +public final class HttpTraceFilter extends OncePerRequestFilter { + /** + * The trace id. + */ + private transient UUID traceId; + + /** + * The event handler. + */ + private final transient HttpTraceEventHandler eventHandler; + + /** + * The constructor. + * @param handler Responsible for HttpTrace events raised by this class. + */ + public HttpTraceFilter(final HttpTraceEventHandler handler) { + super(); + this.eventHandler = handler; + } + + private static UUID generateUUID() { + return UUIDUtils.createUUID(uuid -> false); + } + + @Override + protected void doFilterInternal(final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) + throws ServletException, IOException { + final var requestWrapper = new RequestWrapper(request); + final var responseWrapper = new ContentCachingResponseWrapper(response); + + traceId = generateUUID(); + beforeRequest(requestWrapper); + + try { + filterChain.doFilter(requestWrapper, responseWrapper); + } finally { + afterRequest(responseWrapper); + responseWrapper.copyBodyToResponse(); + } + } + + private void beforeRequest(final RequestWrapper request) { + final var trace = new HttpTrace(); + trace.setTraceId(traceId); + trace.setTimestamp(ZonedDateTime.now(ZoneOffset.UTC)); + trace.setUrl(request.getRequestURI()); + trace.setMethod(request.getMethod()); + trace.setClient(request.getRemoteAddr()); + + trace.setHeaders(new HashMap<>()); + final var headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + final var key = headerNames.nextElement(); + trace.getHeaders().put(key, request.getHeader(key)); + } + + trace.setParameterMap(new HashMap<>()); + final var parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + final var key = parameterNames.nextElement(); + trace.getParameterMap().put(key, request.getHeader(key)); + } + + try { + trace.setBody(new String(request.getRequestBody(), request.getCharacterEncoding())); + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error("Failed to get the request body. [exception=({})]", e.getMessage(), e); + } + } + + eventHandler.sendHttpTraceEvent(trace); + } + + private void afterRequest(final ContentCachingResponseWrapper responseWrapper) { + final var trace = new HttpTrace(); + trace.setTraceId(traceId); + trace.setTimestamp(ZonedDateTime.now(ZoneOffset.UTC)); + trace.setStatus(responseWrapper.getStatus()); + trace.setBody(getResponseAsPayload(responseWrapper)); + + trace.setHeaders(new HashMap<>()); + for (final var key : responseWrapper.getHeaderNames()) { + trace.getHeaders().put(key, responseWrapper.getHeader(key)); + } + + eventHandler.sendHttpTraceEvent(trace); + } + + private String getResponseAsPayload(final ContentCachingResponseWrapper wrappedResponse) { + var response = "ERROR"; + if (wrappedResponse.getContentSize() > 0) { + try { + response = new String(wrappedResponse.getContentAsByteArray(), 0, + wrappedResponse.getContentSize(), + wrappedResponse.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + if (log.isErrorEnabled()) { + log.error("Failed to get the response. [exception=({})]", e.getMessage(), e); + } + } + } + + return response; + } +} diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java b/src/main/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java new file mode 100644 index 000000000..c3050cfe9 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing.internal; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; + +import lombok.extern.log4j.Log4j2; +import org.springframework.util.StreamUtils; + +/** + * Wraps incoming HTTP requests too read the message payload multiple times. + */ +@Log4j2 +public final class RequestWrapper extends HttpServletRequestWrapper { + + /** + * The original request body. + */ + private transient byte[] requestBody; + + /** + * Whether the original request body has been copied. + */ + private transient boolean isBufferFilled; + + /** + * Default constructor. + * @param request The request to be wrapped + */ + public RequestWrapper(final HttpServletRequest request) { + super(request); + } + + /** + * Get the request body of the message. + * @return The request body + * @throws IOException if the request body could not be read + */ + public byte[] getRequestBody() throws IOException { + final var output = isBufferFilled ? requestBody : cloneRequestBody(); + return Arrays.copyOf(output, output.length); + } + + private byte[] cloneRequestBody() throws IOException { + final var inputStream = super.getInputStream(); + if (inputStream != null) { + requestBody = StreamUtils.copyToByteArray(inputStream); + isBufferFilled = true; + } + + return requestBody; + } + + /** + * Get the request body of the message as stream. + * @return The request body as stream + * @throws IOException if the request body could not be read + */ + @Override + public ServletInputStream getInputStream() throws IOException { + return new CustomServletInputStream(getRequestBody()); + } + + @Override + public BufferedReader getReader() { + final var bais = new ByteArrayInputStream(this.requestBody); + return new BufferedReader(new InputStreamReader(bais)); + } + + /** + * Custom input stream returning a clone of the original request. + */ + private static class CustomServletInputStream extends ServletInputStream { + /** + * Copy of the input stream. + */ + private final transient ByteArrayInputStream buffer; + + /** + * Default constructor. + * @param contents The request body. + */ + /* default */ CustomServletInputStream(final byte[] contents) { + super(); + buffer = new ByteArrayInputStream(contents); + } + + @Override + public int read() { + return buffer.read(); + } + + @Override + public boolean isFinished() { + return buffer.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(final ReadListener listener) { + if (log.isErrorEnabled()) { + log.error("Tried to set read listener."); + } + } + } +} diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/internal/package-info.java b/src/main/java/io/dataspaceconnector/filter/httptracing/internal/package-info.java new file mode 100644 index 000000000..f5c6020b8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/internal/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Helper classes for wrapping http messages. + */ + +package io.dataspaceconnector.filter.httptracing.internal; diff --git a/src/main/java/io/dataspaceconnector/filter/httptracing/package-info.java b/src/main/java/io/dataspaceconnector/filter/httptracing/package-info.java new file mode 100644 index 000000000..494604f5e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/filter/httptracing/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The classes for intercepting and logging http communication over + * spring endpoints along with the necessary beans. + */ + +package io.dataspaceconnector.filter.httptracing; diff --git a/src/main/java/io/dataspaceconnector/model/AbstractDescription.java b/src/main/java/io/dataspaceconnector/model/AbstractDescription.java new file mode 100644 index 000000000..fba003052 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/AbstractDescription.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +/** + * The base class for all descriptions. + * @param The type of the class described by the description. + */ +@Data +public class AbstractDescription { + /** + * The overflow for all elements that cannot be mapped. + */ + @JsonIgnore + private Map additional; + + /** + * Add a value to the overflow field. + * If the key already exists it will be overwritten. + * Null for either key or values will be ignored. + * @param key The key. + * @param value The value. + */ + @JsonAnySetter + public void addOverflow(final String key, final String value) { + if (additional == null) { + additional = new HashMap<>(); + } + + if (key == null || value == null) { + return; + } + + additional.put(key, value); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/AbstractEntity.java b/src/main/java/io/dataspaceconnector/model/AbstractEntity.java new file mode 100644 index 000000000..6387b6f4d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/AbstractEntity.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.UUID; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +/** + * Base type for all entities. + */ +@Data +@MappedSuperclass +@Setter(AccessLevel.NONE) +public class AbstractEntity implements Serializable { + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The primary key of all entities. + */ + @Id + @GeneratedValue + @Setter(AccessLevel.PACKAGE) + @Column(name = "id", unique = true, nullable = false) + @EqualsAndHashCode.Exclude + private UUID id; + + /** + * The date when this entity was persisted the first time. + */ + @Column(name = "created_date", nullable = false, updatable = false) + @CreatedDate + @CreationTimestamp + private ZonedDateTime creationDate; + + /** + * The date of the last persistent modification. + */ + @Column(name = "modified_date", nullable = false) + @LastModifiedDate + @UpdateTimestamp + private ZonedDateTime modificationDate; + + /** + * Contains all additional fields that may have been defined but + * could not be mapped. + */ + @ElementCollection + @Setter(AccessLevel.PACKAGE) + private Map additional; + + /** + * Whether this entity is considered deleted. + */ + @Column(name = "deleted") + private boolean deleted; +} diff --git a/src/main/java/io/dataspaceconnector/model/AbstractFactory.java b/src/main/java/io/dataspaceconnector/model/AbstractFactory.java new file mode 100644 index 000000000..4ab4d1e20 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/AbstractFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +/** + * The base factory for factory classes. + * This class creates and updates an entity by using a supplied description. + * @param The type of the entity. + * @param The type of the description. + */ +public interface AbstractFactory> { + /** + * Create a new entity. + * @param desc The description of the entity. + * @return The new entity. + */ + T create(D desc); + + /** + * Update an entity. + * @param entity The entity to be updated. + * @param desc The description of the new entity. + * @return true if changes where performed. + */ + boolean update(T entity, D desc); +} diff --git a/src/main/java/io/dataspaceconnector/model/Agreement.java b/src/main/java/io/dataspaceconnector/model/Agreement.java new file mode 100644 index 000000000..2fbc21105 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Agreement.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.net.URI; +import java.util.List; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * A contract agreement is an agreement between two parties on access + * and usage behaviours. + */ +@Entity +@Table(name = "agreement") +@SQLDelete(sql = "UPDATE agreement SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class Agreement extends AbstractEntity { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The agreement id on provider side. + */ + private URI remoteId; + + /** + * Indicates whether both parties have agreed. + */ + private boolean confirmed; + + /** + * Signal that this agreement expired and may not be used anymore. + */ + private boolean archived; + + /** + * The definition of the contract. + **/ + @Lob + private String value; + + /** + * The artifacts this agreement refers to. + */ + @ManyToMany + private List artifacts; +} diff --git a/src/main/java/io/dataspaceconnector/model/AgreementDesc.java b/src/main/java/io/dataspaceconnector/model/AgreementDesc.java new file mode 100644 index 000000000..2627c011a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/AgreementDesc.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; +import java.util.List; + +/** + * Describes a contract agreement's properties. + */ +@AllArgsConstructor +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class AgreementDesc extends AbstractDescription { + + /** + * The agreement id on provider side. + */ + private URI remoteId; + + /** + * Indicates whether both parties have agreed. + */ + private boolean confirmed; + + /** + * The definition of the contract. + **/ + private String value; + + /** + * The artifacts this agreement refers to. + */ + private List artifacts; +} diff --git a/src/main/java/io/dataspaceconnector/model/AgreementFactory.java b/src/main/java/io/dataspaceconnector/model/AgreementFactory.java new file mode 100644 index 000000000..de3d89908 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/AgreementFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +/** + * Creates and updates a contract. + */ +@Component +public class AgreementFactory implements AbstractFactory { + + /** + * The default remote id. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + /** + * The default value. + */ + static final String DEFAULT_VALUE = ""; + + /** + * Default constructor. + */ + public AgreementFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new contract. + * @param desc The description of the new contract. + * @return The new contract. + * @throws IllegalArgumentException if the description is null. + */ + @Override + public Agreement create(final AgreementDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var agreement = new Agreement(); + agreement.setArtifacts(new ArrayList<>()); + + update(agreement, desc); + + return agreement; + } + + /** + * Update a contract. + * @param agreement The contract to be updated. + * @param desc The new contract description. + * @return True if the contract has been modified. + * @throws IllegalArgumentException if any of the passed arguments is null. + */ + @Override + public boolean update(final Agreement agreement, final AgreementDesc desc) { + Utils.requireNonNull(agreement, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedRemoteId = this.updateRemoteId(agreement, desc.getRemoteId()); + final var hasUpdatedConfirmed = this.updateHasConfirmed(agreement, desc.isConfirmed()); + final var hasUpdatedValue = this.updateValue(agreement, desc.getValue()); + final var hasUpdatedAdditional = this.updateAdditional(agreement, desc.getAdditional()); + + return hasUpdatedRemoteId || hasUpdatedConfirmed || hasUpdatedValue || hasUpdatedAdditional; + } + + private boolean updateRemoteId(final Agreement agreement, final URI remoteId) { + final var newUri = + MetadataUtils.updateUri(agreement.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(agreement::setRemoteId); + + return newUri.isPresent(); + } + + private boolean updateHasConfirmed(final Agreement agreement, final boolean confirmed) { + if (agreement.isConfirmed() != confirmed) { + agreement.setConfirmed(confirmed); + return true; + } + + return false; + } + + private boolean updateValue(final Agreement agreement, final String value) { + final var newValue = MetadataUtils.updateString(agreement.getValue(), value, DEFAULT_VALUE); + newValue.ifPresent(agreement::setValue); + + return newValue.isPresent(); + } + + private boolean updateAdditional( + final Agreement agreement, final Map additional) { + final var newAdditional = MetadataUtils.updateStringMap( + agreement.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(agreement::setAdditional); + + return newAdditional.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/Artifact.java b/src/main/java/io/dataspaceconnector/model/Artifact.java new file mode 100644 index 000000000..f2ed4b8a6 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Artifact.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * An artifact stores and encapsulates data. + */ +@Inheritance +@Entity +@Table(name = "artifact") +@SQLDelete(sql = "UPDATE artifact SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public abstract class Artifact extends AbstractEntity { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The artifact id on provider side. + */ + private URI remoteId; + + /** + * The provider's address for artifact request messages. + */ + private URI remoteAddress; + + /** + * The title of the catalog. + **/ + private String title; + + /** + * The counter of how often the underlying data has been accessed. + */ + private long numAccessed; + + /** + * Indicates whether the artifact should be downloaded automatically. + */ + private boolean automatedDownload; + + /** + * The byte size of the artifact. + */ + private long byteSize; + + /** + * The CRC32C CheckSum of the artifact. + */ + private long checkSum; + + /** + * Increment the data access counter. + */ + public void incrementAccessCounter() { + numAccessed += 1; + } + + /** + * The representations in which this artifact is used. + */ + @ManyToMany(mappedBy = "artifacts") + private List representations; + + /** + * The agreements that refer to this artifact. + */ + @ManyToMany(mappedBy = "artifacts") + private List agreements; +} diff --git a/src/main/java/io/dataspaceconnector/model/ArtifactDesc.java b/src/main/java/io/dataspaceconnector/model/ArtifactDesc.java new file mode 100644 index 000000000..027c18136 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ArtifactDesc.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.net.URI; +import java.net.URL; + +/** + * A description of an artifact. + * This class is consumed when creating or updating an artifact. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ArtifactDesc extends AbstractDescription { + + /** + * The agreement id on provider side. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteId; + + /** + * The provider's address for artifact request messages. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteAddress; + + /** + * The title of the artifact. + */ + private String title; + + /** + * The url of the data location. + */ + private URL accessUrl; + + /** + * The username for authentication at the data location. + */ + private String username; + + /** + * The password for authentication at the data location. + */ + private String password; + + /** + * Some value for storing data locally. + */ + private String value; + + /** + * Indicates whether the artifact should be downloaded automatically. + */ + private boolean automatedDownload = ArtifactFactory.DEFAULT_AUTO_DOWNLOAD; +} diff --git a/src/main/java/io/dataspaceconnector/model/ArtifactFactory.java b/src/main/java/io/dataspaceconnector/model/ArtifactFactory.java new file mode 100644 index 000000000..8cf7d6fb8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ArtifactFactory.java @@ -0,0 +1,240 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.CRC32C; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +/** + * Creates and updates an artifact. + */ +@Component +public final class ArtifactFactory implements AbstractFactory { + + /** + * Default remote id assigned to all artifacts. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + /** + * Default remote address assigned to all artifacts. + */ + public static final URI DEFAULT_REMOTE_ADDRESS = URI.create("genesis"); + + /** + * Default title assigned to all artifacts. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * Default download setting assigned to all artifacts. + */ + public static final boolean DEFAULT_AUTO_DOWNLOAD = false; + + /** + * Default constructor. + */ + public ArtifactFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new artifact. + * @param desc The description of the new artifact. + * @return The new artifact. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public Artifact create(final ArtifactDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var artifact = new ArtifactImpl(); + artifact.setAgreements(new ArrayList<>()); + artifact.setRepresentations(new ArrayList<>()); + + update(artifact, desc); + + return artifact; + } + + /** + * Update an artifact. + * @param artifact The artifact to be updated. + * @param desc The new artifact description. + * @return True if the artifact has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final Artifact artifact, final ArtifactDesc desc) { + Utils.requireNonNull(artifact, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedRemoteId = updateRemoteId(artifact, desc.getRemoteId()); + final var hasUpdatedRemoteAddress = updateRemoteAddress(artifact, desc.getRemoteAddress()); + final var hasUpdatedTitle = updateTitle(artifact, desc.getTitle()); + final var hasUpdatedAutoDownload = updateAutoDownload(artifact, desc.isAutomatedDownload()); + final var hasUpdatedData = updateData(artifact, desc); + final var hasUpdatedAdditional = this.updateAdditional(artifact, desc.getAdditional()); + + return hasUpdatedRemoteId || hasUpdatedRemoteAddress || hasUpdatedTitle + || hasUpdatedAutoDownload || hasUpdatedData || hasUpdatedAdditional; + } + + private boolean updateRemoteId(final Artifact artifact, final URI remoteId) { + final var newUri = + MetadataUtils.updateUri(artifact.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(artifact::setRemoteId); + + return newUri.isPresent(); + } + + private boolean updateRemoteAddress(final Artifact artifact, final URI remoteAddress) { + final var newUri = MetadataUtils + .updateUri(artifact.getRemoteAddress(), remoteAddress, DEFAULT_REMOTE_ADDRESS); + newUri.ifPresent(artifact::setRemoteAddress); + + return newUri.isPresent(); + } + + private boolean updateTitle(final Artifact artifact, final String title) { + final var newTitle = MetadataUtils.updateString(artifact.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(artifact::setTitle); + + return newTitle.isPresent(); + } + + private boolean updateAutoDownload(final Artifact artifact, final boolean autoDownload) { + if (artifact.isAutomatedDownload() != autoDownload) { + artifact.setAutomatedDownload(autoDownload); + return true; + } + + return false; + } + + private boolean updateAdditional(final Artifact artifact, + final Map additional) { + final var newAdditional = + MetadataUtils + .updateStringMap(artifact.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(artifact::setAdditional); + + return newAdditional.isPresent(); + } + + private boolean updateData(final Artifact artifact, final ArtifactDesc desc) { + boolean hasChanged; + if (isRemoteData(desc)) { + hasChanged = updateRemoteData((ArtifactImpl) artifact, desc.getAccessUrl(), + desc.getUsername(), desc.getPassword()); + } else { + hasChanged = updateLocalData((ArtifactImpl) artifact, desc.getValue()); + } + + return hasChanged; + } + + private static boolean isRemoteData(final ArtifactDesc desc) { + return desc.getAccessUrl() != null && desc.getAccessUrl().getPath().length() > 0; + } + + private boolean updateLocalData(final ArtifactImpl artifact, final String value) { + final var newData = new LocalData(); + final var data = value == null ? null : value.getBytes(StandardCharsets.UTF_16); + newData.setValue(data); + + final var oldData = artifact.getData(); + if (oldData instanceof LocalData) { + if (!oldData.equals(newData)) { + artifact.setData(newData); + updateByteSize(artifact, data); + return true; + } + } else { + artifact.setData(newData); + updateByteSize(artifact, data); + return true; + } + + return false; + } + + private boolean updateRemoteData(final ArtifactImpl artifact, final URL accessUrl, + final String username, final String password) { + final var newData = new RemoteData(); + newData.setAccessUrl(accessUrl); + newData.setUsername(username); + newData.setPassword(password); + + final var oldData = artifact.getData(); + if (oldData instanceof RemoteData) { + if (!oldData.equals(newData)) { + artifact.setData(newData); + return true; + } + } else { + artifact.setData(newData); + return true; + } + + return false; + } + + /** + * Update the byte and checksum of an artifact. This will not update + * the actual data. + * @param artifact The artifact which byte and checksum needs to be + * recalculated. + * @param bytes The data. + * @return true if the artifact has been modified. + */ + public boolean updateByteSize(final Artifact artifact, final byte[] bytes) { + var hasChanged = false; + final var checkSum = calculateChecksum(bytes); + + if (bytes != null && artifact.getByteSize() != bytes.length) { + artifact.setByteSize(bytes.length); + hasChanged = true; + } + + if (artifact.getCheckSum() != checkSum) { + artifact.setCheckSum(checkSum); + hasChanged = true; + } + + return hasChanged; + } + + private long calculateChecksum(final byte[] bytes) { + if (bytes == null) { + return 0; + } + + final var checksum = new CRC32C(); + checksum.update(bytes, 0, bytes.length); + return checksum.getValue(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/ArtifactImpl.java b/src/main/java/io/dataspaceconnector/model/ArtifactImpl.java new file mode 100644 index 000000000..88e015b99 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ArtifactImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.OneToOne; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * Contains the data kept in an artifact. + */ +@Entity +@SQLDelete(sql = "UPDATE artifact SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class ArtifactImpl extends Artifact { + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The data stored in the artifact. + **/ + @OneToOne(cascade = { CascadeType.ALL }) + @JsonInclude + @ToString.Exclude + private Data data; +} diff --git a/src/main/java/io/dataspaceconnector/model/Catalog.java b/src/main/java/io/dataspaceconnector/model/Catalog.java new file mode 100644 index 000000000..be4e8d399 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Catalog.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.util.List; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * A catalog groups resources. + */ +@Entity +@Table(name = "catalog") +@SQLDelete(sql = "UPDATE catalog SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class Catalog extends AbstractEntity { + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The title of the catalog. + **/ + private String title; + + /** + * The description of the catalog. + **/ + private String description; + + /** + * The offered resources grouped by the catalog. + **/ + @ManyToMany + private List offeredResources; + + /** + * The requested resources grouped by the catalog. + **/ + @ManyToMany + private List requestedResources; +} diff --git a/src/main/java/io/dataspaceconnector/model/CatalogDesc.java b/src/main/java/io/dataspaceconnector/model/CatalogDesc.java new file mode 100644 index 000000000..f623340a6 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/CatalogDesc.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes a catalog's properties. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CatalogDesc extends AbstractDescription { + /** + * The title of the catalog. + */ + private String title; + + /** + * The description of the catalog. + */ + private String description; +} diff --git a/src/main/java/io/dataspaceconnector/model/CatalogFactory.java b/src/main/java/io/dataspaceconnector/model/CatalogFactory.java new file mode 100644 index 000000000..8ec67c637 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/CatalogFactory.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +/** + * Creates and updates a catalog. + */ +@Component +public class CatalogFactory implements AbstractFactory { + + /** + * Default title assigned to all catalogs. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * Default description assigned to all catalogs. + */ + public static final String DEFAULT_DESCRIPTION = ""; + + /** + * Default constructor. + */ + public CatalogFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new catalog. + * @param desc The description of the new catalog. + * @return The new catalog. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public Catalog create(final CatalogDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var catalog = new Catalog(); + catalog.setOfferedResources(new ArrayList<>()); + catalog.setRequestedResources(new ArrayList<>()); + + update(catalog, desc); + + return catalog; + } + + /** + * Update a catalog. + * @param catalog The catalog to be updated. + * @param desc The new catalog description. + * @return True if the catalog has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final Catalog catalog, final CatalogDesc desc) { + Utils.requireNonNull(catalog, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedTitle = this.updateTitle(catalog, desc.getTitle()); + final var hasUpdatedDesc = this.updateDescription(catalog, desc.getDescription()); + final var hasUpdatedAdditional = this.updateAdditional(catalog, desc.getAdditional()); + + return hasUpdatedTitle || hasUpdatedDesc || hasUpdatedAdditional; + } + + private boolean updateTitle(final Catalog catalog, final String title) { + final var newTitle = MetadataUtils.updateString(catalog.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(catalog::setTitle); + + return newTitle.isPresent(); + } + + private boolean updateDescription(final Catalog catalog, final String description) { + final var newDescription = + MetadataUtils + .updateString(catalog.getDescription(), description, DEFAULT_DESCRIPTION); + newDescription.ifPresent(catalog::setDescription); + + return newDescription.isPresent(); + } + + private boolean updateAdditional(final Catalog catalog, final Map additional) { + final var newAdditional = + MetadataUtils.updateStringMap(catalog.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(catalog::setAdditional); + + return newAdditional.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/Contract.java b/src/main/java/io/dataspaceconnector/model/Contract.java new file mode 100644 index 000000000..4b83fcc65 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Contract.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.List; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * A contract documents access and usage behaviours. + */ +@Entity +@Table(name = "contract") +@SQLDelete(sql = "UPDATE contract SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class Contract extends AbstractEntity { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The contract id on provider side. + */ + private URI remoteId; + + /** + * The consumer signing the contract. + */ + private URI consumer; + + /** + * The provider signing the contract. + */ + private URI provider; + + /** + * The title of the contract. + */ + private String title; + + /** + * Contract start time and date. + */ + @Column(name = "contract_start") + private ZonedDateTime start; + + /** + * Contract end time and date. + */ + @Column(name = "contract_end") + private ZonedDateTime end; + + /** + * The rules used by this contract. + **/ + @ManyToMany + private List rules; + + /** + * The representations in which this contract is used. + */ + @ManyToMany(mappedBy = "contracts") + private List resources; +} diff --git a/src/main/java/io/dataspaceconnector/model/ContractDesc.java b/src/main/java/io/dataspaceconnector/model/ContractDesc.java new file mode 100644 index 000000000..513c4a09c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ContractDesc.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes a contract's properties. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ContractDesc extends AbstractDescription { + + /** + * The contract id on provider side. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteId; + + /** + * The consumer signing the contract. + */ + private URI consumer; + + /** + * The provider signing the contract. + */ + private URI provider; + + /** + * The title of the contract. + */ + private String title; + + /** + * Contract start time and date. + */ + private ZonedDateTime start; + + /** + * Contract end time and date. + */ + private ZonedDateTime end; +} diff --git a/src/main/java/io/dataspaceconnector/model/ContractFactory.java b/src/main/java/io/dataspaceconnector/model/ContractFactory.java new file mode 100644 index 000000000..f6eace87f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ContractFactory.java @@ -0,0 +1,172 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +/** + * Creates and updates a contract. + */ +@Component +public class ContractFactory implements AbstractFactory { + + /** + * Default remote id assigned to all contracts. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + /** + * Default consumer assigned to all contracts. + */ + public static final URI DEFAULT_CONSUMER = URI.create(""); + + /** + * Default provider assigned to all contracts. + */ + public static final URI DEFAULT_PROVIDER = URI.create(""); + + /** + * Default title assigned to all contracts. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * Default constructor. + */ + public ContractFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new contract. + * @param desc The description of the new contract. + * @return The new contract. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public Contract create(final ContractDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var contract = new Contract(); + contract.setRules(new ArrayList<>()); + contract.setResources(new ArrayList<>()); + + update(contract, desc); + + return contract; + } + + /** + * Update a contract. + * @param contract The contract to be updated. + * @param desc The new contract description. + * @return True if the contract has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final Contract contract, final ContractDesc desc) { + Utils.requireNonNull(contract, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedRemoteId = this.updateRemoteId(contract, desc.getRemoteId()); + final var hasUpdatedConsumer = this.updateConsumer(contract, desc.getConsumer()); + final var hasUpdatedProvider = this.updateProvider(contract, desc.getProvider()); + final var hasUpdatedTitle = this.updateTitle(contract, desc.getTitle()); + final var hasUpdatedAdditional = this.updateAdditional(contract, desc.getAdditional()); + + final var hasUpdatedTime = this.updateTime(contract, contract.getStart(), desc.getEnd()); + + return hasUpdatedRemoteId || hasUpdatedConsumer || hasUpdatedProvider || hasUpdatedTitle + || hasUpdatedTime || hasUpdatedAdditional; + } + + private boolean updateRemoteId(final Contract contract, final URI remoteId) { + final var newUri = + MetadataUtils.updateUri(contract.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(contract::setRemoteId); + + return newUri.isPresent(); + } + + private boolean updateConsumer(final Contract contract, final URI consumer) { + final var newUri = + MetadataUtils.updateUri(contract.getConsumer(), consumer, DEFAULT_CONSUMER); + newUri.ifPresent(contract::setConsumer); + + return newUri.isPresent(); + } + + private boolean updateProvider(final Contract contract, final URI provider) { + final var newUri = + MetadataUtils.updateUri(contract.getProvider(), provider, DEFAULT_PROVIDER); + newUri.ifPresent(contract::setProvider); + + return newUri.isPresent(); + } + + private boolean updateTitle(final Contract contract, final String title) { + final var newTitle = MetadataUtils.updateString(contract.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(contract::setTitle); + + return newTitle.isPresent(); + } + + private boolean updateTime(final Contract contract, final ZonedDateTime start, + final ZonedDateTime end) { + final var defaultTime = ZonedDateTime.now(ZoneOffset.UTC); + final var newStart = MetadataUtils.updateDate(contract.getStart(), start, defaultTime); + final var newEnd = MetadataUtils.updateDate(contract.getEnd(), end, defaultTime); + + // Validate the state of the contract with the new times + var realStart = newStart.orElseGet(contract::getStart); + var realEnd = newEnd.orElseGet(contract::getEnd); + + if (realStart.isAfter(realEnd)) { + // Invalid state, fix up + realStart = realEnd; + } + + // Reiterate the operation + final var finalStartValue = + MetadataUtils.updateDate(contract.getStart(), realStart, defaultTime); + final var finalEndValue = MetadataUtils.updateDate(contract.getEnd(), realEnd, defaultTime); + + finalStartValue.ifPresent(contract::setStart); + finalEndValue.ifPresent(contract::setEnd); + + return finalStartValue.isPresent() || finalEndValue.isPresent(); + } + + private boolean updateAdditional( + final Contract contract, final Map additional) { + final var newAdditional = MetadataUtils.updateStringMap( + contract.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(contract::setAdditional); + + return newAdditional.isPresent(); + } + +} diff --git a/src/main/java/io/dataspaceconnector/model/ContractRule.java b/src/main/java/io/dataspaceconnector/model/ContractRule.java new file mode 100644 index 000000000..79b20f045 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ContractRule.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.net.URI; +import java.util.List; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * A ContractRule defines a rule that should be enforced. + */ +@Entity +@Table(name = "contractrule") +@SQLDelete(sql = "UPDATE contractrule SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class ContractRule extends AbstractEntity { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The rule id on provider side. + */ + private URI remoteId; + + /** + * The title of the rule. + */ + private String title; + + /** + * The definition of the rule. + **/ + @Lob + private String value; + + /** + * The contracts in which this rule is used. + */ + @ManyToMany(mappedBy = "rules") + private List contracts; +} diff --git a/src/main/java/io/dataspaceconnector/model/ContractRuleDesc.java b/src/main/java/io/dataspaceconnector/model/ContractRuleDesc.java new file mode 100644 index 000000000..0a9c2b021 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ContractRuleDesc.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.net.URI; + +/** + * The description of a contract rule. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ContractRuleDesc extends AbstractDescription { + + /** + * The rule id on provider side. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteId; + + /** + * The title of the rule. + */ + private String title; + + /** + * The rule to be enforced. + */ + private String value; +} diff --git a/src/main/java/io/dataspaceconnector/model/ContractRuleFactory.java b/src/main/java/io/dataspaceconnector/model/ContractRuleFactory.java new file mode 100644 index 000000000..799d867be --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ContractRuleFactory.java @@ -0,0 +1,125 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +/** + * Creates and updates a ContractRule. + */ +@Component +public class ContractRuleFactory implements AbstractFactory { + + /** + * The default remote id assigned to all contract rules. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + /** + * The default title assigned to all contract rules. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * The default rule assigned to all contract rules. + */ + public static final String DEFAULT_RULE = ""; + + /** + * Default constructor. + */ + public ContractRuleFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new ContractRule. + * @param desc The description of the new ContractRule. + * @return The new ContractRule. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public ContractRule create(final ContractRuleDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var rule = new ContractRule(); + rule.setContracts(new ArrayList<>()); + + update(rule, desc); + + return rule; + } + + /** + * Update a ContractRule. + * @param contractRule The ContractRule to be updated. + * @param desc The new ContractRule description. + * @return True if the ContractRule has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final ContractRule contractRule, final ContractRuleDesc desc) { + Utils.requireNonNull(contractRule, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedRemoteId = this.updateRemoteId(contractRule, desc.getRemoteId()); + final var hasUpdatedTitle = this.updateTitle(contractRule, desc.getTitle()); + final var hasUpdatedRule = this.updateRule(contractRule, desc.getValue()); + final var hasUpdatedAdditional = this.updateAdditional(contractRule, desc.getAdditional()); + + return hasUpdatedRemoteId || hasUpdatedTitle || hasUpdatedRule || hasUpdatedAdditional; + } + + private boolean updateRemoteId(final ContractRule contractRule, final URI remoteId) { + final var newUri = MetadataUtils.updateUri( + contractRule.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(contractRule::setRemoteId); + + return newUri.isPresent(); + } + + private boolean updateTitle(final ContractRule contractRule, final String title) { + final var newTitle = + MetadataUtils.updateString(contractRule.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(contractRule::setTitle); + + return newTitle.isPresent(); + } + + private boolean updateRule(final ContractRule contractRule, final String rule) { + final var newRule = MetadataUtils.updateString(contractRule.getValue(), rule, DEFAULT_RULE); + newRule.ifPresent(contractRule::setValue); + + return newRule.isPresent(); + } + + private boolean updateAdditional( + final ContractRule contractRule, final Map additional) { + final var newAdditional = MetadataUtils.updateStringMap( + contractRule.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(contractRule::setAdditional); + + return newAdditional.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/Data.java b/src/main/java/io/dataspaceconnector/model/Data.java new file mode 100644 index 000000000..63b1a23f0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Data.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * The interface for describing data in the backend. + */ +@Entity +@Inheritance +@Table(name = "data") +@SQLDelete(sql = "UPDATE data SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.NONE) +@EqualsAndHashCode +@RequiredArgsConstructor +public class Data { + + /** + * The primary key of the data. + */ + @Id + @GeneratedValue + @JsonIgnore + @ToString.Exclude + private Long id; + + /** + * Whether this entity is considered deleted. + */ + @Column(name = "deleted") + private boolean deleted; + +} diff --git a/src/main/java/io/dataspaceconnector/model/EndpointId.java b/src/main/java/io/dataspaceconnector/model/EndpointId.java new file mode 100644 index 000000000..5dd822d14 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/EndpointId.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Embeddable; +import java.io.Serializable; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Describes an endpoint splitting it into the general path and the + * id of the resource affected by this endpoint. + */ +@Data +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +public class EndpointId implements Serializable { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The base path. + */ + @JsonIgnore + private String basePath; + + /** + * The uuid. + */ + @JsonIgnore + private UUID resourceId; +} diff --git a/src/main/java/io/dataspaceconnector/model/LocalData.java b/src/main/java/io/dataspaceconnector/model/LocalData.java new file mode 100644 index 000000000..1563ec01f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/LocalData.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Entity; +import javax.persistence.Lob; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * Simple wrapper for data stored in the internal database. + */ +@Entity +@Getter +@SQLDelete(sql = "UPDATE data SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +@Setter(AccessLevel.PACKAGE) +public class LocalData extends Data { + + /** + * The data. + */ + @Lob + private byte[] value; +} diff --git a/src/main/java/io/dataspaceconnector/model/OfferedResource.java b/src/main/java/io/dataspaceconnector/model/OfferedResource.java new file mode 100644 index 000000000..35fa0d2fb --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/OfferedResource.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import lombok.EqualsAndHashCode; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.util.List; + +/** + * Describes resources offered by this connector. + */ +@SQLDelete(sql = "UPDATE resource SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Entity +@EqualsAndHashCode(callSuper = true) +public final class OfferedResource extends Resource { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + protected OfferedResource() { + super(); + } + + /** + * The catalogs in which this resource is used. + */ + @ManyToMany(mappedBy = "offeredResources") + private List catalogs; + + /** + * {@inheritDoc} + */ + @Override + public void setCatalogs(final List catalogList) { + this.catalogs = catalogList; + } + + /** + * {@inheritDoc} + */ + @Override + public List getCatalogs() { + return catalogs; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/OfferedResourceDesc.java b/src/main/java/io/dataspaceconnector/model/OfferedResourceDesc.java new file mode 100644 index 000000000..38aceba7c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/OfferedResourceDesc.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes an offered resource. Use this structure to create + * or update an offered resource. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OfferedResourceDesc extends ResourceDesc { +} diff --git a/src/main/java/io/dataspaceconnector/model/OfferedResourceFactory.java b/src/main/java/io/dataspaceconnector/model/OfferedResourceFactory.java new file mode 100644 index 000000000..77e75ee8c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/OfferedResourceFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import org.springframework.stereotype.Component; + +/** + * Creates and updates a resource. + */ +@Component +public final class OfferedResourceFactory extends ResourceFactory { + @Override + protected OfferedResource createInternal(final OfferedResourceDesc desc) { + return new OfferedResource(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/QueryInput.java b/src/main/java/io/dataspaceconnector/model/QueryInput.java new file mode 100644 index 000000000..7453447cc --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/QueryInput.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Query for a backend. + */ +@Schema( + name = "QueryInput", + description = "Query parameters, headers and path variables as maps", + oneOf = QueryInput.class, + example = "{\n" + + " \"headers\": {\n" + + " \"key\": \"value\"\n" + + " },\n" + + " \"params\": {\n" + + " \"key\": \"value\"\n" + + " },\n" + + " \"pathVariables\": {\n" + + " \"key\": \"value\"\n" + + " }\n" + + "}" +) +@Data +public class QueryInput { + + /** + * Headers to use for querying a backend. + */ + private Map headers = new ConcurrentHashMap<>(); + + /** + * Params to use for querying a backend. + */ + private Map params = new ConcurrentHashMap<>(); + + /** + * Path variables to use for querying a backend. + */ + private Map pathVariables = new ConcurrentHashMap<>(); + + /** + * Path extending the base path of an api request. + */ + private String optional; + +} diff --git a/src/main/java/io/dataspaceconnector/model/RemoteData.java b/src/main/java/io/dataspaceconnector/model/RemoteData.java new file mode 100644 index 000000000..cf073ae15 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RemoteData.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URL; +import javax.persistence.Entity; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * Bundles information needed for accessing remote backends. + */ +@Entity +@Getter +@Setter(AccessLevel.PACKAGE) +@SQLDelete(sql = "UPDATE data SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class RemoteData extends Data { + /** + * Access url of the backend. + */ + private URL accessUrl; + + /** + * The username for accessing the backend. + */ + private String username; + + /** + * The password for accessing the backend. + */ + private String password; +} diff --git a/src/main/java/io/dataspaceconnector/model/Representation.java b/src/main/java/io/dataspaceconnector/model/Representation.java new file mode 100644 index 000000000..8fd101467 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Representation.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.net.URI; +import java.util.List; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * A representation describes how data is presented. + */ +@Entity +@Table(name = "representation") +@SQLDelete(sql = "UPDATE representation SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@RequiredArgsConstructor +public class Representation extends AbstractEntity { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The representation id on provider side. + */ + private URI remoteId; + + /** + * The title of the representation. + */ + private String title; + + /** + * The media type expressed by this representation. + */ + private String mediaType; + + /** + * The language used by this representation. + */ + private String language; + + /** + * "Standard followed at representation level, i.e. it governs + * the serialization of an abstract content like RDF/XML." + */ + private String standard; + + /** + * The artifacts associated with this representation. + */ + @ManyToMany + private List artifacts; + + /** + * The resources associated with this representation. + */ + @ManyToMany(mappedBy = "representations") + private List resources; +} diff --git a/src/main/java/io/dataspaceconnector/model/RepresentationDesc.java b/src/main/java/io/dataspaceconnector/model/RepresentationDesc.java new file mode 100644 index 000000000..1074bf069 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RepresentationDesc.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes a representation. Use this for creating or updating a representation. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RepresentationDesc extends AbstractDescription { + + /** + * The representation id on provider side. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteId; + + /** + * The title of the representation. + */ + private String title; + + /** + * The media type expressed by this representation. + */ + private String mediaType; + + /** + * The language used by this representation. + */ + private String language; + + /** + * "Standard followed at representation level, i.e. it governs + * the serialization of an abstract content like RDF/XML." + */ + private String standard; +} diff --git a/src/main/java/io/dataspaceconnector/model/RepresentationFactory.java b/src/main/java/io/dataspaceconnector/model/RepresentationFactory.java new file mode 100644 index 000000000..3a7c3c5ed --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RepresentationFactory.java @@ -0,0 +1,153 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Creates and updates a representation. + */ +@Component +public class RepresentationFactory implements AbstractFactory { + + /** + * The default remote id assigned to all representations. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + /** + * The default title assigned to all representations. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * The default language assigned to all representations. + */ + public static final String DEFAULT_LANGUAGE = "EN"; + + /** + * The default media type assigned to all representations. + */ + public static final String DEFAULT_MEDIA_TYPE = ""; + + /** + * The default standard assigned to all representations. + */ + public static final String DEFAULT_STANDARD = ""; + + /** + * Create a new representation. + * @param desc The description of the new representation. + * @return The new representation. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public Representation create(final RepresentationDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var representation = new Representation(); + representation.setArtifacts(new ArrayList<>()); + representation.setResources(new ArrayList<>()); + + update(representation, desc); + + return representation; + } + + /** + * Update a representation. + * @param representation The representation to be updated. + * @param desc The new representation description. + * @return True if the representation has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final Representation representation, final RepresentationDesc desc) { + Utils.requireNonNull(representation, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedRemoteId = this.updateRemoteId(representation, desc.getRemoteId()); + final var hasUpdatedTitle = this.updateTitle(representation, desc.getTitle()); + final var hasUpdatedMediaType = this.updateMediaType(representation, desc.getMediaType()); + final var hasUpdatedLanguage = this.updateLanguage(representation, desc.getLanguage()); + final var hasUpdatedStandard = this.updateStandard(representation, desc.getStandard()); + final var hasUpdatedAdditional = + this.updateAdditional(representation, desc.getAdditional()); + + return hasUpdatedRemoteId || hasUpdatedTitle || hasUpdatedLanguage || hasUpdatedMediaType + || hasUpdatedStandard || hasUpdatedAdditional; + } + + private boolean updateRemoteId(final Representation representation, final URI remoteId) { + final var newUri = MetadataUtils.updateUri( + representation.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(representation::setRemoteId); + + return newUri.isPresent(); + } + + private boolean updateTitle(final Representation representation, final String title) { + final var newTitle = + MetadataUtils.updateString(representation.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(representation::setTitle); + + return newTitle.isPresent(); + } + + private boolean updateLanguage(final Representation representation, final String language) { + final var newLanguage = + MetadataUtils + .updateString(representation.getLanguage(), language, DEFAULT_LANGUAGE); + newLanguage.ifPresent(representation::setLanguage); + + return newLanguage.isPresent(); + } + + private boolean updateMediaType(final Representation representation, final String mediaType) { + final var newMediaType = + MetadataUtils + .updateString(representation.getMediaType(), mediaType, DEFAULT_MEDIA_TYPE); + newMediaType.ifPresent(representation::setMediaType); + + return newMediaType.isPresent(); + } + + private boolean updateStandard(final Representation representation, final String standard) { + final var newAdditional = + MetadataUtils + .updateString(representation.getStandard(), standard, DEFAULT_STANDARD); + newAdditional.ifPresent(representation::setStandard); + + return newAdditional.isPresent(); + } + + private boolean updateAdditional( + final Representation representation, final Map additional) { + final var newAdditional = MetadataUtils.updateStringMap( + representation.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(representation::setAdditional); + + return newAdditional.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/RequestedResource.java b/src/main/java/io/dataspaceconnector/model/RequestedResource.java new file mode 100644 index 000000000..946c49230 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RequestedResource.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.net.URI; +import java.util.List; + +/** + * Describes resource requested by this connector. + */ +@Entity +@SQLDelete(sql = "UPDATE resource SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +public final class RequestedResource extends Resource { + + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The resource id on provider side. + */ + private URI remoteId; + + /** + * Default constructor. + */ + protected RequestedResource() { + super(); + } + + /** + * The catalogs in which this resource is used. + */ + @ManyToMany(mappedBy = "requestedResources") + private List catalogs; + + /** + * {@inheritDoc} + */ + @Override + public void setCatalogs(final List catalogList) { + this.catalogs = catalogList; + } + + /** + * {@inheritDoc} + */ + @Override + public List getCatalogs() { + return catalogs; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/RequestedResourceDesc.java b/src/main/java/io/dataspaceconnector/model/RequestedResourceDesc.java new file mode 100644 index 000000000..4affadc02 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RequestedResourceDesc.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Describes a requested resource. Use this to create or + * update an requested resource. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RequestedResourceDesc extends ResourceDesc { + + /** + * The resource id on provider side. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private URI remoteId; +} diff --git a/src/main/java/io/dataspaceconnector/model/RequestedResourceFactory.java b/src/main/java/io/dataspaceconnector/model/RequestedResourceFactory.java new file mode 100644 index 000000000..733a84c84 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/RequestedResourceFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import io.dataspaceconnector.utils.MetadataUtils; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * Creates and updates a resource. + */ +@Component +public final class RequestedResourceFactory + extends ResourceFactory { + + /** + * The default remote id assigned to all requested resources. + */ + public static final URI DEFAULT_REMOTE_ID = URI.create("genesis"); + + @Override + protected RequestedResource createInternal(final RequestedResourceDesc desc) { + return new RequestedResource(); + } + + @Override + protected boolean updateInternal( + final RequestedResource resource, final RequestedResourceDesc desc) { + return updateRemoteId(resource, desc.getRemoteId()); + } + + private boolean updateRemoteId(final RequestedResource resource, final URI remoteId) { + final var newUri = + MetadataUtils.updateUri(resource.getRemoteId(), remoteId, DEFAULT_REMOTE_ID); + newUri.ifPresent(resource::setRemoteId); + + return newUri.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/Resource.java b/src/main/java/io/dataspaceconnector/model/Resource.java new file mode 100644 index 000000000..ffaa09d65 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/Resource.java @@ -0,0 +1,138 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Inheritance; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import java.net.URI; +import java.util.List; + +import io.dataspaceconnector.exceptions.ResourceException; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; +import org.springframework.data.annotation.Version; + +/** + * A resource describes offered or requested data. + */ +@Entity +@Inheritance +@Getter +@Setter(AccessLevel.PACKAGE) +@EqualsAndHashCode(callSuper = true) +@SQLDelete(sql = "UPDATE resource SET deleted=true WHERE id=?") +@Where(clause = "deleted = false") +@Table(name = "resource") +@RequiredArgsConstructor +public class Resource extends AbstractEntity { + /** + * Serial version uid. + **/ + private static final long serialVersionUID = 1L; + + /** + * The title of the resource. + */ + private String title; + + /** + * The description of the resource. + */ + private String description; + + /** + * The keywords of the resource. + */ + @ElementCollection + private List keywords; + + /** + * The publisher of the resource. + */ + private URI publisher; + + /** + * The owner of the resource. + */ + private URI sovereign; + + /** + * The language of the resource. + */ + private String language; + + /** + * The licence of the resource. + */ + private URI licence; + + /** + * The endpoint of the resource. + */ + private URI endpointDocumentation; + + /** + * The version of the resource. + */ + @Version + private long version; + + /** + * The representation available for the resource. + */ + @ManyToMany + private List representations; + + /** + * The contracts available for the resource. + */ + @ManyToMany + private List contracts; + + /** + * Set the catalogs used by this resource. + * @param catalogList The catalog list. + */ + public void setCatalogs(final List catalogList) { + /* + NOTE: Offered and Requested Resource override this function. + */ + throw new ResourceException("Not implemented"); + } + + /** + * Get the list of catalogs used by this resource. + * @return The list of catalogs used by this resource. + */ + public List getCatalogs() { + /* + NOTE: Offered and Requested Resource override this function + so that exception should never be returned. Throw exception + here so that a missing override crashes really load. + */ + throw new ResourceException("Not implemented"); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/ResourceDesc.java b/src/main/java/io/dataspaceconnector/model/ResourceDesc.java new file mode 100644 index 000000000..ea8540f68 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ResourceDesc.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.List; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Base class for describing resources. + * @param The type of the resource. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ResourceDesc extends AbstractDescription { + + /** + * The title of the resource. + */ + private String title; + + /** + * The description of the resource. + */ + private String description; + + /** + * The keywords of the resource. + */ + private List keywords; + + /** + * The publisher of the resource. + */ + private URI publisher; + + /** + * The language of the resource. + */ + private String language; + + /** + * The licence of the resource. + */ + private URI licence; + + /** + * The owner of the resource. + */ + private URI sovereign; + + /** + * The endpoint of the resource. + */ + private URI endpointDocumentation; +} diff --git a/src/main/java/io/dataspaceconnector/model/ResourceFactory.java b/src/main/java/io/dataspaceconnector/model/ResourceFactory.java new file mode 100644 index 000000000..a2a9c25df --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/ResourceFactory.java @@ -0,0 +1,276 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MetadataUtils; +import io.dataspaceconnector.utils.Utils; + +/** + * Base class for creating and updating resources. + * @param The resource type. + * @param The description type. + */ +public abstract class ResourceFactory> + implements AbstractFactory { + + /** + * The default title assigned to all resources. + */ + public static final String DEFAULT_TITLE = ""; + + /** + * The default description assigned to all resources. + */ + public static final String DEFAULT_DESCRIPTION = ""; + + /** + * The default keywords assigned to all resources. + */ + public static final List DEFAULT_KEYWORDS = List.of("DSC"); + + /** + * The default publisher assigned to all resources. + */ + public static final URI DEFAULT_PUBLISHER = URI.create(""); + + /** + * The default language assigned to all resources. + */ + public static final String DEFAULT_LANGUAGE = ""; + + /** + * The default licence assigned to all resources. + */ + public static final URI DEFAULT_LICENCE = URI.create(""); + + /** + * The default sovereign assigned to all resources. + */ + public static final URI DEFAULT_SOVEREIGN = URI.create(""); + + /** + * The default endpoint documentation assigned to all resources. + */ + public static final URI DEFAULT_ENDPOINT_DOCS = URI.create(""); + + /** + * Default constructor. + */ + public ResourceFactory() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Create a new resource. + * @param desc The description of the new resource. + * @return The new resource. + * @throws IllegalArgumentException if desc is null. + */ + @Override + public T create(final D desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var resource = createInternal(desc); + resource.setRepresentations(new ArrayList<>()); + resource.setContracts(new ArrayList<>()); + resource.setCatalogs(new ArrayList<>()); + + update(resource, desc); + + return resource; + } + + /** + * Create a new resource. Implement type specific stuff here. + * @param desc The description passed to the factory. + * @return The new resource. + */ + protected abstract T createInternal(D desc); + + /** + * Update a resource. + * @param resource The resource to be updated. + * @param desc The new resource description. + * @return True if the resource has been modified. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Override + public boolean update(final T resource, final D desc) { + Utils.requireNonNull(resource, ErrorMessages.ENTITY_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var hasUpdatedTitle = updateTitle(resource, desc.getTitle()); + final var hasUpdatedDesc = updateDescription(resource, desc.getDescription()); + final var hasUpdatedKeywords = updateKeywords(resource, desc.getKeywords()); + final var hasUpdatedPublisher = updatePublisher(resource, desc.getPublisher()); + final var hasUpdatedLanguage = updateLanguage(resource, desc.getLanguage()); + final var hasUpdatedLicence = updateLicence(resource, desc.getLicence()); + final var hasUpdatedSovereign = updateSovereign(resource, desc.getSovereign()); + final var hasUpdatedEndpointDocs = + updateEndpointDocs(resource, desc.getEndpointDocumentation()); + final var hasUpdatedAdditional = updateAdditional(resource, desc.getAdditional()); + + final var hasChildUpdated = updateInternal(resource, desc); + + final var hasUpdated = hasChildUpdated || hasUpdatedTitle || hasUpdatedDesc + || hasUpdatedKeywords || hasUpdatedPublisher || hasUpdatedLanguage + || hasUpdatedLicence || hasUpdatedSovereign || hasUpdatedEndpointDocs + || hasUpdatedAdditional; + + if (hasUpdated) { + resource.setVersion(resource.getVersion() + 1); + } + + return hasUpdated; + } + + /** + * Update a resource. Implement type specific stuff here. + * @param resource The resource to be updated. + * @param desc The description passed to the factory. + * @return true if the resource has been modified. + */ + protected boolean updateInternal(final T resource, final D desc) { + return false; + } + + /** + * Update a resource's title. + * @param resource The resource. + * @param title The new title. + * @return true if the resource's title has been modified. + */ + protected final boolean updateTitle(final Resource resource, final String title) { + final var newTitle = MetadataUtils.updateString(resource.getTitle(), title, DEFAULT_TITLE); + newTitle.ifPresent(resource::setTitle); + + return newTitle.isPresent(); + } + + /** + * Update a resource's description. + * @param resource The resource. + * @param description The new description. + * @return true if the resource's description has been modified. + */ + protected final boolean updateDescription(final Resource resource, final String description) { + final var newDesc = MetadataUtils.updateString( + resource.getDescription(), description, DEFAULT_DESCRIPTION); + newDesc.ifPresent(resource::setDescription); + + return newDesc.isPresent(); + } + + /** + * Update a resource's keywords. + * @param resource The resource. + * @param keywords The new keywords. + * @return true if the resource's keywords have been modified. + */ + protected final boolean updateKeywords(final Resource resource, final List keywords) { + final var newKeys = + MetadataUtils.updateStringList(resource.getKeywords(), keywords, DEFAULT_KEYWORDS); + newKeys.ifPresent(resource::setKeywords); + + return newKeys.isPresent(); + } + + /** + * Update a resource's publisher. + * @param resource The resource. + * @param publisher The new publisher. + * @return true if the resource's publisher has been modified. + */ + protected final boolean updatePublisher(final Resource resource, final URI publisher) { + final var newPublisher = + MetadataUtils.updateUri(resource.getPublisher(), publisher, DEFAULT_PUBLISHER); + newPublisher.ifPresent(resource::setPublisher); + + return newPublisher.isPresent(); + } + + /** + * Update a resource's language. + * @param resource The resource. + * @param language The new language. + * @return true if the resource's language has been modified. + */ + protected final boolean updateLanguage(final Resource resource, final String language) { + final var newLanguage = + MetadataUtils.updateString(resource.getLanguage(), language, DEFAULT_LANGUAGE); + newLanguage.ifPresent(resource::setLanguage); + + return newLanguage.isPresent(); + } + + /** + * Update a resource's licence. + * @param resource The resource. + * @param licence The new licence. + * @return true if the resource's licence has been modified. + */ + protected final boolean updateLicence(final Resource resource, final URI licence) { + final var newLicence = + MetadataUtils.updateUri(resource.getLicence(), licence, DEFAULT_LICENCE); + newLicence.ifPresent(resource::setLicence); + + return newLicence.isPresent(); + } + + /** + * Update a resource's sovereign. + * @param resource The resource. + * @param sovereign The new sovereign. + * @return true if the resource's sovereign has been modified. + */ + protected final boolean updateSovereign(final Resource resource, final URI sovereign) { + final var newPublisher = + MetadataUtils.updateUri(resource.getSovereign(), sovereign, DEFAULT_SOVEREIGN); + newPublisher.ifPresent(resource::setSovereign); + + return newPublisher.isPresent(); + } + + /** + * Update a resource's endpoint documentation. + * @param resource The resource. + * @param endpointDocs The new endpoint documentation. + * @return true if the resource's endpoint documentation has been modified. + */ + protected final boolean updateEndpointDocs(final Resource resource, final URI endpointDocs) { + final var newPublisher = MetadataUtils.updateUri( + resource.getEndpointDocumentation(), endpointDocs, DEFAULT_ENDPOINT_DOCS); + newPublisher.ifPresent(resource::setEndpointDocumentation); + + return newPublisher.isPresent(); + } + + private boolean updateAdditional( + final Resource resource, final Map additional) { + final var newAdditional = MetadataUtils.updateStringMap( + resource.getAdditional(), additional, new HashMap<>()); + newAdditional.ifPresent(resource::setAdditional); + + return newAdditional.isPresent(); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/TimeInterval.java b/src/main/java/io/dataspaceconnector/model/TimeInterval.java new file mode 100644 index 000000000..54d8e080c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/TimeInterval.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.time.ZonedDateTime; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * Inner class for a time interval format. + */ +@Getter +@Setter(AccessLevel.PUBLIC) +@EqualsAndHashCode +@RequiredArgsConstructor +public class TimeInterval { + + /** + * The start date. + */ + private ZonedDateTime start; + + /** + * The end date. + */ + private ZonedDateTime end; +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDesc.java new file mode 100644 index 000000000..cfdc40c63 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDesc.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Class for all artifact request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public final class ArtifactRequestMessageDesc extends MessageDesc { + + /** + * The requested artifact of the message. + */ + private URI requestedArtifact; + + /** + * The transfer contract of the message. + */ + private URI transferContract; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param artifact The requested artifact. + * @param contract The transfer contract. + */ + public ArtifactRequestMessageDesc(final URI recipient, final URI artifact, final URI contract) { + super(recipient); + + this.requestedArtifact = artifact; + this.transferContract = contract; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDesc.java new file mode 100644 index 000000000..afc5ae9f2 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDesc.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ArtifactResponseMessageDesc extends MessageDesc { + + /** + * The ids of the correlation message. + */ + private URI correlationMessage; + + /** + * The transfer contract of the message. + */ + private URI transferContract; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param message The correlation message. + * @param contract The transfer contract. + */ + public ArtifactResponseMessageDesc(final URI recipient, final URI message, final URI contract) { + super(recipient); + + this.correlationMessage = message; + this.transferContract = contract; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDesc.java new file mode 100644 index 000000000..2a24b6c22 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ContractAgreementMessageDesc extends MessageDesc { + + /** + * The ids of the correlation message. + */ + private URI correlationMessage; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param message The correlation message. + */ + public ContractAgreementMessageDesc(final URI recipient, final URI message) { + super(recipient); + + this.correlationMessage = message; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDesc.java new file mode 100644 index 000000000..630cd9eaa --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; + +/** + * Class for all description request message parameters. + */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class ContractRejectionMessageDesc extends MessageDesc { + + /** + * The ids of the correlation message. + */ + private URI correlationMessage; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param message The correlation message. + */ + public ContractRejectionMessageDesc(final URI recipient, final URI message) { + super(recipient); + + this.correlationMessage = message; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/ContractRequestMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/ContractRequestMessageDesc.java new file mode 100644 index 000000000..345338b13 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/ContractRequestMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ContractRequestMessageDesc extends MessageDesc { + + /** + * The transfer contract of the message. + */ + private URI transferContract; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param contract The transfer contract. + */ + public ContractRequestMessageDesc(final URI recipient, final URI contract) { + super(recipient); + + this.transferContract = contract; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDesc.java new file mode 100644 index 000000000..025ff1914 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DescriptionRequestMessageDesc extends MessageDesc { + + /** + * The requested element of the message. + */ + private URI requestedElement; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param element The requested element. + */ + public DescriptionRequestMessageDesc(final URI recipient, final URI element) { + super(recipient); + + this.requestedElement = element; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDesc.java new file mode 100644 index 000000000..fa3c1b6ec --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DescriptionResponseMessageDesc extends MessageDesc { + + /** + * The ids of the correlation message. + */ + private URI correlationMessage; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param message The correlation message. + */ + public DescriptionResponseMessageDesc(final URI recipient, final URI message) { + super(recipient); + + this.correlationMessage = message; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/LogMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/LogMessageDesc.java new file mode 100644 index 000000000..558b37583 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/LogMessageDesc.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LogMessageDesc extends MessageDesc { + /** + * All args constructor. + * + * @param recipient The message's recipient. + */ + public LogMessageDesc(final URI recipient) { + super(recipient); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/MessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/MessageDesc.java new file mode 100644 index 000000000..a24f5355b --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/MessageDesc.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Base class for all messages descriptions. + */ +@Data +@EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor +public class MessageDesc { + /** + * The message's recipient. + */ + private URI recipient; +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDesc.java new file mode 100644 index 000000000..2e7682b72 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDesc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.net.URI; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class MessageProcessedNotificationMessageDesc extends MessageDesc { + + /** + * The ids of the correlation message. + */ + private URI correlationMessage; + + /** + * All args constructor. + * + * @param recipient The message's recipient. + * @param message The correlation message. + */ + public MessageProcessedNotificationMessageDesc(final URI recipient, final URI message) { + super(recipient); + + this.correlationMessage = message; + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/NotificationMessageDesc.java b/src/main/java/io/dataspaceconnector/model/messages/NotificationMessageDesc.java new file mode 100644 index 000000000..d7f4ea1db --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/NotificationMessageDesc.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Class for all description request message parameters. + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class NotificationMessageDesc extends MessageDesc { + /** + * All args constructor. + * + * @param recipient The message's recipient. + */ + public NotificationMessageDesc(final URI recipient) { + super(recipient); + } +} diff --git a/src/main/java/io/dataspaceconnector/model/messages/package-info.java b/src/main/java/io/dataspaceconnector/model/messages/package-info.java new file mode 100644 index 000000000..d935249b0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/messages/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains descriptions about different IDS Messages. A description + * bundles information needed for constructing IDS messages. + */ +package io.dataspaceconnector.model.messages; diff --git a/src/main/java/io/dataspaceconnector/model/package-info.java b/src/main/java/io/dataspaceconnector/model/package-info.java new file mode 100644 index 000000000..2c753cc0f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains the different entities modelled by the Dataspace Connector and + * factories for creating and updating those entities. + */ +package io.dataspaceconnector.model; diff --git a/src/main/java/io/dataspaceconnector/model/templates/AgreementTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/AgreementTemplate.java new file mode 100644 index 000000000..f3a232e57 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/AgreementTemplate.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import java.net.URI; + +import io.dataspaceconnector.model.AgreementDesc; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Describes a agreement and all its dependencies. + */ +@Getter +@NoArgsConstructor +@RequiredArgsConstructor +public class AgreementTemplate { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Agreement parameters. + */ + private @NonNull AgreementDesc desc; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/ArtifactTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/ArtifactTemplate.java new file mode 100644 index 000000000..c05f19402 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/ArtifactTemplate.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import java.net.URI; + +import io.dataspaceconnector.model.ArtifactDesc; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + + +/** + * Describes an artifact and all its dependencies. + */ +@Getter +@NoArgsConstructor +@RequiredArgsConstructor +@EqualsAndHashCode +public class ArtifactTemplate { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Artifact parameters. + */ + private @NonNull ArtifactDesc desc; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/ContractTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/ContractTemplate.java new file mode 100644 index 000000000..7948fc54d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/ContractTemplate.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import io.dataspaceconnector.model.ContractDesc; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.net.URI; +import java.util.List; + +/** + * Describes a contract and all its dependencies. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@RequiredArgsConstructor +public class ContractTemplate { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Contract parameters. + */ + @Setter(AccessLevel.NONE) + private @NonNull ContractDesc desc; + + /** + * List of rule templates. + */ + private List rules; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/RepresentationTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/RepresentationTemplate.java new file mode 100644 index 000000000..e3c70be50 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/RepresentationTemplate.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import java.net.URI; +import java.util.List; + +import io.dataspaceconnector.model.RepresentationDesc; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * Describes a representation and all its dependencies. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@RequiredArgsConstructor +public class RepresentationTemplate { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Representation parameters. + */ + @Setter(AccessLevel.NONE) + private @NonNull RepresentationDesc desc; + + /** + * List of artifact templates. + */ + private List artifacts; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/ResourceTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/ResourceTemplate.java new file mode 100644 index 000000000..423797770 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/ResourceTemplate.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import java.net.URI; +import java.util.List; + +import io.dataspaceconnector.model.AbstractDescription; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * Describes a resource and all its dependencies. + * @param The resource type. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@RequiredArgsConstructor +public class ResourceTemplate> { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Resource parameters. + */ + @Setter(AccessLevel.NONE) + private @NonNull D desc; + + /** + * List of representation templates. + */ + private List representations; + + /** + * List of contract templates. + */ + private List contracts; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/RuleTemplate.java b/src/main/java/io/dataspaceconnector/model/templates/RuleTemplate.java new file mode 100644 index 000000000..c784fedce --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/RuleTemplate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + +import java.net.URI; + +import io.dataspaceconnector.model.ContractRuleDesc; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Describes a contract rule and all its dependencies. + */ +@Getter +@NoArgsConstructor +@RequiredArgsConstructor +@AllArgsConstructor +public class RuleTemplate { + + /** + * Old remote id. + */ + private URI oldRemoteId; + + /** + * Rule parameters. + */ + private @NonNull ContractRuleDesc desc; +} diff --git a/src/main/java/io/dataspaceconnector/model/templates/package-info.java b/src/main/java/io/dataspaceconnector/model/templates/package-info.java new file mode 100644 index 000000000..3cfa1a7d7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/model/templates/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains templates for describing graphs of entities. + */ +package io.dataspaceconnector.model.templates; diff --git a/src/main/java/io/dataspaceconnector/package-info.java b/src/main/java/io/dataspaceconnector/package-info.java new file mode 100644 index 000000000..3b8828946 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is the root package for all io.dataspaceconnector related packages. + */ +package io.dataspaceconnector; diff --git a/src/main/java/io/dataspaceconnector/repositories/AgreementRepository.java b/src/main/java/io/dataspaceconnector/repositories/AgreementRepository.java new file mode 100644 index 000000000..8ff4512b9 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/AgreementRepository.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import java.util.UUID; + +import io.dataspaceconnector.model.Agreement; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link Agreement}. + */ +@Repository +public interface AgreementRepository extends BaseEntityRepository { + + /** + * Set the status of an agreement to confirmed. + * @param entityId The id of the agreement. + */ + @Modifying + @Query("UPDATE Agreement a " + + "SET a.confirmed = true " + + "WHERE a.id = :entityId " + + "AND a.archived = false " + + "AND a.deleted = false") + void confirmAgreement(UUID entityId); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/ArtifactRepository.java b/src/main/java/io/dataspaceconnector/repositories/ArtifactRepository.java new file mode 100644 index 000000000..987a72921 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/ArtifactRepository.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import java.net.URI; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Artifact; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link Artifact}. + */ +@Repository +public interface ArtifactRepository extends RemoteEntityRepository { + + /** + * Finds all artifacts of a specific resource. + * + * @param resourceId ID of the resource + * @return list of all artifacts of the resource + */ + @Query("SELECT a " + + "FROM Artifact a, Representation r, OfferedResource o " + + "WHERE o.id = :resourceId " + + "AND r MEMBER OF o.representations " + + "AND a MEMBER OF r.artifacts " + + "AND a.deleted = false " + + "AND r.deleted = false " + + "AND o.deleted = false") + List findAllByResourceId(UUID resourceId); + + /** + * Finds all artifacts referenced in a specific agreement. + * + * @param agreementId ID of the agreement + * @return list of all artifacts referenced in the agreement + */ + @Query("SELECT a " + + "FROM Artifact a INNER JOIN Agreement ag ON a MEMBER OF ag.artifacts " + + "WHERE ag.id = :agreementId " + + "AND ag.deleted = false " + + "AND a.deleted = false") + List findAllByAgreement(UUID agreementId); + + /** + * Search for all agreements signed for requested resources by this connector as consumer. + * @param artifactId The artifact. + * @return The list of agreement ids. + */ + @Query("SELECT ag.remoteId " + + "FROM Artifact a, Agreement ag " + + "WHERE a.id = :artifactId " + + "AND a.deleted = false " + + "AND ag.deleted = false " + + "AND ag.remoteId <> 'aced00057372000c6a6176612e6e65742e555" + + "249ac01782e439e49ab0300014c0006737472696e677400124c6a" + + "6176612f6c616e672f537472696e673b787074000767656e6573697378' " + + "AND ag.archived = false " + + "AND ag.confirmed = true " + + "AND ag MEMBER OF a.agreements") + List findRemoteOriginAgreements(UUID artifactId); + + /** + * Set the artifacts data. + * @param artifactId The artifact. + * @param checkSum The new CRC32C checksum. + * @param size The new size in bytes. + */ + @Modifying + @Query("UPDATE Artifact a " + + "SET a.checkSum=:checkSum, a.byteSize=:size " + + "WHERE a.id = :artifactId " + + "AND a.deleted = false") + void setArtifactData(UUID artifactId, long checkSum, long size); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/BaseEntityRepository.java b/src/main/java/io/dataspaceconnector/repositories/BaseEntityRepository.java new file mode 100644 index 000000000..ee3f388b0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/BaseEntityRepository.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.AbstractEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.UUID; + +/** + * The base repository for all entities of type {@link AbstractEntity}. + * @param The entity type. + */ +@NoRepositoryBean +public interface BaseEntityRepository extends JpaRepository { +} diff --git a/src/main/java/io/dataspaceconnector/repositories/CatalogRepository.java b/src/main/java/io/dataspaceconnector/repositories/CatalogRepository.java new file mode 100644 index 000000000..6afb5b38c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/CatalogRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.Catalog; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link Catalog}. + */ +@Repository +public interface CatalogRepository extends BaseEntityRepository { +} diff --git a/src/main/java/io/dataspaceconnector/repositories/ContractRepository.java b/src/main/java/io/dataspaceconnector/repositories/ContractRepository.java new file mode 100644 index 000000000..89ab91502 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/ContractRepository.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Contract; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link + * io.dataspaceconnector.model.Catalog}. + */ +@Repository +public interface ContractRepository extends BaseEntityRepository { + + /** + * Finds all contracts applicable for a specific artifact. + * + * @param artifactId ID of the artifact + * @return list of contracts applicable for the artifact + */ + @Query("SELECT c " + + "FROM Contract c INNER JOIN OfferedResource o ON c MEMBER OF o.contracts " + + "INNER JOIN Representation r ON r MEMBER OF o.representations " + + "INNER JOIN Artifact a ON a MEMBER OF r.artifacts WHERE a.id = :artifactId " + + "AND c.deleted = false " + + "AND o.deleted = false " + + "AND r.deleted = false " + + "AND a.deleted = false") + List findAllByArtifactId(UUID artifactId); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/DataRepository.java b/src/main/java/io/dataspaceconnector/repositories/DataRepository.java new file mode 100644 index 000000000..05bc685ce --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/DataRepository.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.Data; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link Data}. + */ +@Repository +public interface DataRepository extends JpaRepository { + /** + * Set new local data for an entity. + * + * @param entityId The entity id. + * @param data The new data. + */ + @Modifying + @Query("UPDATE LocalData a " + + "SET a.value = :data " + + "WHERE a.id = :entityId") + void setLocalData(Long entityId, byte[] data); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/OfferedResourcesRepository.java b/src/main/java/io/dataspaceconnector/repositories/OfferedResourcesRepository.java new file mode 100644 index 000000000..bb202db3d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/OfferedResourcesRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.OfferedResource; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link OfferedResource}. + */ +@Repository +public interface OfferedResourcesRepository extends BaseEntityRepository { +} diff --git a/src/main/java/io/dataspaceconnector/repositories/RemoteEntityRepository.java b/src/main/java/io/dataspaceconnector/repositories/RemoteEntityRepository.java new file mode 100644 index 000000000..ce9d7e637 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/RemoteEntityRepository.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.AbstractEntity; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.NoRepositoryBean; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository for entities identifiable by remote id. + * + * @param Type of the entity. + */ +@NoRepositoryBean +public interface RemoteEntityRepository extends BaseEntityRepository { + /* + NOTE: Maybe return the complete object? Depends on the general usage and the bandwidth + needed for the Object. + */ + + /** + * Find an entity id by its remote id. + * + * @param remoteId The remote id. + * @return The id of the entity. + */ + @Query("SELECT a.id " + + "FROM #{#entityName} a " + + "WHERE a.remoteId = :remoteId " + + "AND a.deleted = false") + Optional identifyByRemoteId(URI remoteId); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/RepresentationRepository.java b/src/main/java/io/dataspaceconnector/repositories/RepresentationRepository.java new file mode 100644 index 000000000..7b42690f4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/RepresentationRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.Representation; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link Representation}. + */ +@Repository +public interface RepresentationRepository extends RemoteEntityRepository { +} diff --git a/src/main/java/io/dataspaceconnector/repositories/RequestedResourcesRepository.java b/src/main/java/io/dataspaceconnector/repositories/RequestedResourcesRepository.java new file mode 100644 index 000000000..fa19c702a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/RequestedResourcesRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.RequestedResource; +import org.springframework.stereotype.Repository; + +/** + * The repository containing all objects of type {@link RequestedResource}. + */ +@Repository +public interface RequestedResourcesRepository extends RemoteEntityRepository { +} diff --git a/src/main/java/io/dataspaceconnector/repositories/RuleRepository.java b/src/main/java/io/dataspaceconnector/repositories/RuleRepository.java new file mode 100644 index 000000000..bab6e8efd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/RuleRepository.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.ContractRule; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +/** + * The repository containing all objects of type {@link ContractRule}. + */ +@Repository +public interface RuleRepository extends BaseEntityRepository { + /** + * Finds all rules in a specific contract. + * + * @param contractId ID of the contract + * @return list of all rules in the contract + */ + @Query("SELECT r " + + "FROM ContractRule r INNER JOIN Contract c ON r MEMBER OF c.rules " + + "WHERE c.id = :contractId " + + "AND r.deleted = false " + + "AND c.deleted = false") + List findAllByContract(UUID contractId); +} diff --git a/src/main/java/io/dataspaceconnector/repositories/package-info.java b/src/main/java/io/dataspaceconnector/repositories/package-info.java new file mode 100644 index 000000000..27ccfbd3c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/repositories/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains the repositories for the different datatypes defined + * by the Dataspace Connector. + * Do not use these directly in your code. Consider using one of + * the resource services instead. + */ +package io.dataspaceconnector.repositories; diff --git a/src/main/java/io/dataspaceconnector/services/ArtifactRetriever.java b/src/main/java/io/dataspaceconnector/services/ArtifactRetriever.java new file mode 100644 index 000000000..4149a507c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ArtifactRetriever.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import java.io.InputStream; +import java.net.URI; +import java.util.UUID; + +import io.dataspaceconnector.model.QueryInput; + +/** + * Performs an artifact request for an artifact. + */ +public interface ArtifactRetriever { + /** + * Perform an artifact request for a given artifact. + * @param artifactId The artifact whose data should be updated. + * @param recipient The target connector holding the artifact's data. + * @param transferContract The contract authorizing the data transfer. + * @return The artifact's data. + */ + InputStream retrieve(UUID artifactId, URI recipient, URI transferContract); + + /** + * Perform an artifact request for a given artifact with query parameters. + * @param artifactId The artifact whose data should be updated. + * @param recipient The target connector holding the artifact's data. + * @param transferContract The contract authorizing the data transfer. + * @param queryInput The data query for specifying the requested data. + * @return The artifact's data. + */ + InputStream retrieve(UUID artifactId, URI recipient, URI transferContract, + QueryInput queryInput); +} diff --git a/src/main/java/io/dataspaceconnector/services/BlockingArtifactReceiver.java b/src/main/java/io/dataspaceconnector/services/BlockingArtifactReceiver.java new file mode 100644 index 000000000..08655b62f --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/BlockingArtifactReceiver.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.services.messages.types.ArtifactRequestService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MessageUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.UUID; + +/** + * Performs an artifact request for an artifact. All functions will block till the request is + * completed. + */ +@Component +@Log4j2 +@RequiredArgsConstructor +public class BlockingArtifactReceiver implements ArtifactRetriever { + + /** + * Used for sending an artifact request message. + */ + private final @NonNull ArtifactRequestService messageService; + + /** + * Used for accessing artifacts and their data. + */ + private final @NonNull ArtifactService artifactService; + + /** + * {@inheritDoc} + */ + @Override + public InputStream retrieve(final UUID artifactId, final URI recipient, + final URI transferContract) { + return retrieve(artifactId, recipient, transferContract, null); + } + + /** + * {@inheritDoc} + */ + @Override + public InputStream retrieve(final UUID artifactId, final URI recipient, + final URI transferContract, final QueryInput queryInput) + throws PolicyRestrictionException { + final var artifact = artifactService.get(artifactId); + final var response = messageService.sendMessage(recipient, + artifact.getRemoteId(), transferContract, queryInput); + if (!messageService.validateResponse(response)) { + final var content = messageService.getResponseContent(response); + if (log.isDebugEnabled()) { + log.debug("Data could not be loaded. [content=({})]", content); + } + + throw new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + } + + final var data = MessageUtils.extractPayloadFromMultipartMessage(response); + return new ByteArrayInputStream(Base64Utils.decodeFromString(data)); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/EntityPersistenceService.java b/src/main/java/io/dataspaceconnector/services/EntityPersistenceService.java new file mode 100644 index 000000000..dfae4f2d3 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/EntityPersistenceService.java @@ -0,0 +1,238 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.persistence.PersistenceException; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractRequest; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.AgreementDesc; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.RelationServices; +import io.dataspaceconnector.services.resources.TemplateBuilder; +import io.dataspaceconnector.services.usagecontrol.ContractManager; +import io.dataspaceconnector.utils.EndpointUtils; +import io.dataspaceconnector.utils.IdsUtils; +import io.dataspaceconnector.utils.MessageUtils; +import io.dataspaceconnector.utils.TemplateUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.jose4j.base64url.Base64; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * This service offers methods for saving contract agreements as well as metadata and data requested + * from other connectors to the database. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class EntityPersistenceService { + + /** + * Service for contract agreements. + */ + private final @NonNull AgreementService agreementService; + + /** + * Service for updating artifact data. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Service for linking agreements and artifacts. + */ + private final @NonNull RelationServices.AgreementArtifactLinker linker; + + /** + * Service for contract processing. + */ + private final @NonNull ContractManager contractManager; + + /** + * Service for deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Template builder. + */ + private final @NonNull TemplateBuilder tempBuilder; + + /** + * Save contract agreement to database (consumer side). + * + * @param agreement The ids contract agreement. + * @return The id of the stored contract agreement. + * @throws PersistenceException If the contract agreement could not be saved. + */ + public UUID saveContractAgreement(final ContractAgreement agreement) + throws PersistenceException { + try { + final var agreementId = agreement.getId(); + final var rdf = IdsUtils.toRdf(agreement); + + final var desc = new AgreementDesc(agreementId, true, rdf, null); + + // Save agreement to return its id. + return agreementService.create(desc).getId(); + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("Could not store contract agreement. [exception=({})]", + e.getMessage(), e); + } + throw new PersistenceException("Could not store contract agreement.", e); + } + } + + /** + * Builds a contract agreement from a contract request and saves this agreement to the database + * with relation to the targeted artifacts (provider side). + * + * @param request The ids contract request. + * @param targetList List of artifacts. + * @param issuer The issuer connector id. + * @return The id of the stored contract agreement. + * @throws PersistenceException If the contract agreement could not be saved. + */ + public ContractAgreement buildAndSaveContractAgreement( + final ContractRequest request, final List targetList, final URI issuer) + throws PersistenceException { + UUID agreementUuid = null; + try { + // Get base URL of application and path to agreements API. + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().toUriString(); + final var path = ResourceControllers.AgreementController.class.getAnnotation( + RequestMapping.class).value()[0]; + + // Persist empty agreement to generate UUID. + agreementUuid = agreementService.create(new AgreementDesc()).getId(); + + // Construct ID of contract agreement (URI) using base URL, path and the UUID. + final var agreementId = URI.create(baseUrl + path + "/" + agreementUuid); + + // Build the contract agreement using the constructed ID + final var agreement = contractManager.buildContractAgreement(request, + agreementId, issuer); + + // Iterate over all targets to get the UUIDs of the corresponding artifacts. + final var artifactList = new ArrayList(); + for (final var target : targetList) { + final var uuid = EndpointUtils.getUUIDFromPath(target); + artifactList.add(uuid); + } + + final var rdf = IdsUtils.toRdf(agreement); + + final var desc = new AgreementDesc(); + desc.setConfirmed(false); + desc.setValue(rdf); + + // Update agreement in database using its previously set id. + agreementService.update(EndpointUtils.getUUIDFromPath(agreement.getId()), desc); + + // Add artifacts to agreement using the linker. + linker.add(EndpointUtils.getUUIDFromPath(agreement.getId()), + new HashSet<>(artifactList)); + + return agreement; + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("Could not store contract agreement. [exception=({})]", + e.getMessage(), e); + } + + // if agreement cannot be saved, remove empty agreement from database + if (agreementUuid != null) { + agreementService.delete(agreementUuid); + } + + throw new PersistenceException("Could not store contract agreement.", e); + } + } + + /** + * Validate response and save resource to database. + * + * @param response The response message map. + * @param artifactList List of requested artifacts. + * @param download Indicated whether the artifact is going to be downloaded automatically. + * @param remoteUrl The provider's url for receiving artifact request messages. + */ + public void saveMetadata(final Map response, final List artifactList, + final boolean download, final URI remoteUrl) + throws PersistenceException, MessageResponseException, IllegalArgumentException { + // Exceptions handled at a higher level. + final var payload = MessageUtils.extractPayloadFromMultipartMessage(response); + final var resource = deserializationService.getResource(payload); + + try { + final var resourceTemplate = + TemplateUtils.getResourceTemplate(resource); + final var representationTemplateList = + TemplateUtils.getRepresentationTemplates(resource, artifactList, download, + remoteUrl); + + resourceTemplate.setRepresentations(representationTemplateList); + + // Save all entities. + tempBuilder.build(resourceTemplate); + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("Could not store resource. [exception=({})]", e.getMessage(), e); + } + throw new PersistenceException("Could not store resource.", e); + } + } + + /** + * Save data and return the uri of the respective artifact. + * + * @param response The response message. + * @param remoteId The artifact id. + * @throws MessageResponseException If the message response could not be processed. + * @throws ResourceNotFoundException If the artifact could not be found. + */ + public void saveData(final Map response, final URI remoteId) + throws MessageResponseException, ResourceNotFoundException { + final var base64Data = MessageUtils.extractPayloadFromMultipartMessage(response); + final var artifactId = artifactService.identifyByRemoteId(remoteId); + final var artifact = artifactService.get(artifactId.get()); + + artifactService.setData(artifact.getId(), + new ByteArrayInputStream(Base64.decode(base64Data))); + if (log.isDebugEnabled()) { + log.debug("Updated data from artifact. [target=({})]", artifactId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/EntityResolver.java b/src/main/java/io/dataspaceconnector/services/EntityResolver.java new file mode 100644 index 000000000..08be73d55 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/EntityResolver.java @@ -0,0 +1,289 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import de.fraunhofer.iais.eis.ContractAgreement; +import io.dataspaceconnector.exceptions.InvalidResourceException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.SelfLinkCreationException; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.services.ids.builder.IdsCatalogBuilder; +import io.dataspaceconnector.services.ids.builder.IdsContractBuilder; +import io.dataspaceconnector.services.ids.builder.IdsRepresentationBuilder; +import io.dataspaceconnector.services.ids.builder.IdsResourceBuilder; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.CatalogService; +import io.dataspaceconnector.services.resources.ContractService; +import io.dataspaceconnector.services.resources.RepresentationService; +import io.dataspaceconnector.services.resources.ResourceService; +import io.dataspaceconnector.services.resources.RuleService; +import io.dataspaceconnector.services.usagecontrol.AllowAccessVerifier; +import io.dataspaceconnector.utils.EndpointUtils; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.io.InputStream; + +import org.springframework.stereotype.Service; + +/** + * This service offers methods for finding entities by their identifying URI. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class EntityResolver { + + /** + * Service for artifacts. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Service for representations. + */ + private final @NonNull RepresentationService representationService; + + /** + * Service for offered resources. + */ + private final @NonNull ResourceService offerService; + + /** + * Service for catalogs. + */ + private final @NonNull CatalogService catalogService; + + /** + * Service for contract offers. + */ + private final @NonNull ContractService contractService; + + /** + * Service for contract rules. + */ + private final @NonNull RuleService ruleService; + + /** + * Service for contract agreements. + */ + private final @NonNull AgreementService agreementService; + + /** + * Service for building ids objects. + */ + private final @NonNull IdsCatalogBuilder catalogBuilder; + + /** + * Service for building ids resource. + */ + private final @NonNull IdsResourceBuilder offerBuilder; + + /** + * Service for building ids artifact. + */ + private final @NonNull IdsArtifactBuilder artifactBuilder; + + /** + * Service for building ids representation. + */ + private final @NonNull IdsRepresentationBuilder representationBuilder; + + /** + * Service for building ids contract. + */ + private final @NonNull IdsContractBuilder contractBuilder; + + /** + * Skips the data access verification. + */ + private final @NonNull AllowAccessVerifier allowAccessVerifier; + + /** + * Performs a artifact requests. + */ + private final @NonNull BlockingArtifactReceiver artifactReceiver; + + /** + * Service for deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Return any connector entity by its id. + * + * @param elementId The entity id. + * @return The respective object. + * @throws ResourceNotFoundException If the resource could not be found. + * @throws IllegalArgumentException If the resource is null or the elementId. + */ + public AbstractEntity getEntityById(final URI elementId) throws ResourceNotFoundException { + Utils.requireNonNull(elementId, ErrorMessages.URI_NULL); + + try { + final var endpointId = EndpointUtils.getEndpointIdFromPath(elementId); + final var basePath = endpointId.getBasePath(); + final var entityId = endpointId.getResourceId(); + + final var pathEnum = EndpointUtils.getBasePathEnumFromString(basePath); + + // Find the right service and return the requested element. + switch (Objects.requireNonNull(pathEnum)) { + case ARTIFACTS: + return artifactService.get(entityId); + case REPRESENTATIONS: + return representationService.get(entityId); + case OFFERS: + return offerService.get(entityId); + case CATALOGS: + return catalogService.get(entityId); + case CONTRACTS: + return contractService.get(entityId); + case RULES: + return ruleService.get(entityId); + case AGREEMENTS: + return agreementService.get(entityId); + default: + return null; + } + } catch (Exception exception) { + if (log.isDebugEnabled()) { + log.debug("Resource not found. [exception=({}), elementId=({})]", + exception.getMessage(), elementId, exception); + } + throw new ResourceNotFoundException(ErrorMessages.EMTPY_ENTITY.toString(), exception); + } + } + + /** + * Translate a connector entity to an ids rdf string. + * + * @param Type of the entity. + * @param entity The connector's entity. + * @return A rdf string of an ids object. + */ + public String getEntityAsRdfString(final T entity) + throws InvalidResourceException { + // NOTE Maybe the builder class could be found without the ugly if array? + try { + if (entity instanceof Artifact) { + final var artifact = artifactBuilder.create((Artifact) entity); + return Objects.requireNonNull(artifact).toRdf(); + } else if (entity instanceof OfferedResource) { + final var resource = offerBuilder.create((OfferedResource) entity); + return Objects.requireNonNull(resource).toRdf(); + } else if (entity instanceof Representation) { + final var representation = representationBuilder.create((Representation) entity); + return Objects.requireNonNull(representation).toRdf(); + } else if (entity instanceof Catalog) { + final var catalog = catalogBuilder.create((Catalog) entity); + return Objects.requireNonNull(catalog).toRdf(); + } else if (entity instanceof Contract) { + final var catalog = contractBuilder.create((Contract) entity); + return Objects.requireNonNull(catalog).toRdf(); + } else if (entity instanceof Agreement) { + final var agreement = (Agreement) entity; + return agreement.getValue(); + } else if (entity instanceof ContractRule) { + final var rule = (ContractRule) entity; + return rule.getValue(); + } + } catch (SelfLinkCreationException exception) { + if (log.isWarnEnabled()) { + log.warn("Could not provide ids object. [entity=({}), exception=({})]", + entity, exception.getMessage(), exception); + } + throw exception; + } catch (Exception exception) { + // If we do not allow requesting an object type, respond with exception. + if (log.isWarnEnabled()) { + log.warn("Could not provide ids object. [entity=({}), exception=({})]", + entity, exception.getMessage(), exception); + } + throw new InvalidResourceException("No provided description for requested element."); + } + + // If we do not allow requesting an object type, respond with exception. + if (log.isDebugEnabled()) { + log.debug("Not a requestable ids object. [entity=({})]", entity); + } + throw new InvalidResourceException("No provided description for requested element."); + } + + /** + * Return artifact by uri. This will skip the access control. + * + * @param requestedArtifact The artifact uri. + * @param queryInput Http query for data request. + * @return Artifact from database. + */ + public InputStream getDataByArtifactId(final URI requestedArtifact, + final QueryInput queryInput) { + final var endpoint = EndpointUtils.getUUIDFromPath(requestedArtifact); + return artifactService.getData(allowAccessVerifier, artifactReceiver, endpoint, queryInput); + } + + /** + * Get agreement by remote id. + * + * @param id The remote id (at provider side). + * @return The artifact of the database. + * @throws ResourceNotFoundException If the resource could not be found. + */ + public Agreement getAgreementByUri(final URI id) throws ResourceNotFoundException { + final var uuid = EndpointUtils.getUUIDFromPath(id); + return agreementService.get(uuid); + } + + /** + * Get stored contract agreement for requested element. + * + * @param target The requested element. + * @return The respective contract agreement. + */ + public List getContractAgreementsByTarget(final URI target) { + final var uuid = EndpointUtils.getUUIDFromPath(target); + final var artifact = artifactService.get(uuid); + + final var agreements = artifact.getAgreements(); + final var agreementList = new ArrayList(); + for (final var agreement : agreements) { + final var value = agreement.getValue(); + final var idsAgreement = deserializationService.getContractAgreement(value); + agreementList.add(idsAgreement); + } + return agreementList; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/EntityUpdateService.java b/src/main/java/io/dataspaceconnector/services/EntityUpdateService.java new file mode 100644 index 000000000..46319134d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/EntityUpdateService.java @@ -0,0 +1,173 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import java.net.URI; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.Resource; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.services.ids.updater.ArtifactUpdater; +import io.dataspaceconnector.services.ids.updater.RepresentationUpdater; +import io.dataspaceconnector.services.ids.updater.RequestedResourceUpdater; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.RelationServices; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.SelfLinkHelper; +import io.dataspaceconnector.utils.Utils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +/** + * This service offers method for updating entities. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class EntityUpdateService { + + /** + * Updates a requested resource by using an ids resource. + */ + private final @NonNull RequestedResourceUpdater requestedResourceUpdater; + + /** + * Updates a representation by using an ids representations. + */ + private final @NonNull RepresentationUpdater representationUpdater; + + /** + * Updates an artifact by using an ids artifacts. + */ + private final @NonNull ArtifactUpdater artifactUpdater; + + /** + * Service for agreements. + */ + private final @NonNull AgreementService agreementService; + + /** + * Service for linking artifacts to agreement. + */ + private final @NonNull RelationServices.AgreementArtifactLinker agreementArtifactLinker; + + /** + * Service for artifacts. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Update database resource. + * + * @param resource The ids resource. + */ + public void updateResource(final Resource resource) { + try { + final var updated = requestedResourceUpdater.update(resource); + if (log.isDebugEnabled()) { + log.debug("Updated resource. [uri=({})]", SelfLinkHelper.getSelfLink(updated)); + } + + final var representations = resource.getRepresentation(); + for (final var representation : Utils + .requireNonNull(representations, ErrorMessages.LIST_NULL)) { + updateRepresentation(representation); + } + } catch (ResourceNotFoundException | IllegalArgumentException exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to update resource. [uri=({})]", resource.getId()); + } + } + } + + /** + * Update database representation that is known to the consumer. + * + * @param representation The ids representation. + */ + public void updateRepresentation(final Representation representation) { + try { + final var updated = representationUpdater.update(representation); + if (log.isDebugEnabled()) { + log.debug("Updated representation. [uri=({})]", + SelfLinkHelper.getSelfLink(updated)); + } + + final var artifacts = representation.getInstance(); + for (final var artifact : Utils.requireNonNull(artifacts, ErrorMessages.LIST_NULL)) { + updateArtifact((Artifact) artifact); + } + } catch (ResourceNotFoundException | IllegalArgumentException exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to update representation. [uri=({})]", representation.getId()); + } + } + } + + /** + * Update database artifact that is known to the consumer. + * + * @param artifact The ids artifact. + */ + public void updateArtifact(final Artifact artifact) { + try { + final var updated = artifactUpdater.update(artifact); + log.debug("Updated artifact. [uri=({})]", SelfLinkHelper.getSelfLink(updated)); + } catch (ResourceNotFoundException exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to update artifact. [uri=({})]", artifact.getId()); + } + } + } + + /** + * Set confirmed boolean to true. + * + * @param agreement The database agreement. + * @return true if the agreement has been confirmed. + */ + public boolean confirmAgreement(final Agreement agreement) { + try { + return agreementService.confirmAgreement(agreement); + } catch (ResourceNotFoundException exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to confirm agreement. [uri=({})]", agreement.getId()); + } + + return false; + } + } + + /** + * Link list of artifacts to a contract agreement. + * + * @param artifactIds List of artifact ids. + * @param agreementId The id of the agreement. + */ + public final void linkArtifactToAgreement(final List artifactIds, final UUID agreementId) { + final var localArtifacts = Utils.toStream(artifactIds) + .map(x -> artifactService.identifyByRemoteId(x).get()).collect(Collectors.toSet()); + agreementArtifactLinker.add(agreementId, localArtifacts); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/HttpService.java b/src/main/java/io/dataspaceconnector/services/HttpService.java new file mode 100644 index 000000000..93d5d21be --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/HttpService.java @@ -0,0 +1,237 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import kotlin.Pair; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import okhttp3.Credentials; +import okhttp3.HttpUrl; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Map; + +/** + * This class builds up http or httpS endpoint connections and sends GET requests. + */ +@Service +@RequiredArgsConstructor +public class HttpService { + + /** + * Service for building and sending http requests. + */ + private final @NonNull de.fraunhofer.isst.ids.framework.communication.http.HttpService + httpSvc; + + /** + * The request method. + */ + public enum Method { + /** + * http GET. + */ + GET, + // OPTIONS, + // HEAD, + // POST, + // PUT, + // PATCH, + // DELETE + } + + + /** + * The http request arguments. + */ + @Data + public static class HttpArgs { + /** + * The request headers. + */ + private Map headers; + + /** + * The request parameters. + */ + private Map params; + + /** + * Authentication information. Will overwrite entry in headers. + */ + private Pair auth; + } + + + /** + * The response to a http request. + */ + @Data + @EqualsAndHashCode + public static class Response { + /** + * The response code. + */ + private int code; + + /** + * The response body. + */ + private InputStream body; + } + + /** + * Perform a get request. + * + * @param target The recipient of the request. + * @param args The request arguments. + * @return The response. + * @throws IOException if the request failed. + * @throws IllegalArgumentException if any of the parameters is null. + */ + public Response get(final URL target, final HttpArgs args) throws IOException { + Utils.requireNonNull(target, ErrorMessages.URI_NULL); + Utils.requireNonNull(args, ErrorMessages.HTTP_ARGS_NULL); + + final var urlBuilder = HttpUrl.parse(target.toString()).newBuilder(); + + if (args.getParams() != null) { + for (final var key : args.getParams().keySet()) { + urlBuilder.addQueryParameter(key, args.getParams().get(key)); + } + } + + final var targetUri = urlBuilder.build().uri(); + + okhttp3.Response response; + if (args.getHeaders() == null) { + response = httpSvc.get(targetUri); + } else { + /* + Make a copy of the headers and insert sensitive data only into the copy. + */ + final var headerCopy = Map.copyOf(args.getHeaders()); + if (args.getAuth() != null) { + headerCopy.put("Authorization", + Credentials.basic(args.getAuth().getFirst(), args.getAuth().getSecond())); + } + + response = httpSvc.getWithHeaders(targetUri, headerCopy); + } + + final var output = new Response(); + output.setCode(response.code()); + output.setBody(response.body().byteStream()); + + return output; + } + + /** + * Perform a get request. + * + * @param target The recipient of the request. + * @param input The query inputs. + * @return The response. + * @throws IOException if the request failed. + */ + public Response get(final URL target, final QueryInput input) throws IOException { + final var url = (input == null) ? buildTargetUrl(target, null) + : buildTargetUrl(target, input.getOptional()); + return this.get(url, toArgs(input)); + } + + /** + * Perform a get request. + * + * @param target The recipient of the request. + * @param input The query inputs. + * @param auth The authentication information. + * @return The response. + * @throws IOException if the request failed. + */ + public Response get(final URL target, final QueryInput input, final Pair auth) + throws IOException { + final var url = (input == null) ? buildTargetUrl(target, null) + : buildTargetUrl(target, input.getOptional()); + return this.get(url, toArgs(input, auth)); + } + + private URL buildTargetUrl(final URL target, final String optional) { + final var urlBuilder = HttpUrl.parse(target.toString()).newBuilder(); + if (optional != null) { + urlBuilder.addEncodedPathSegment(optional); + } + + return urlBuilder.build().url(); + } + + /** + * Perform a http request. + * + * @param method The request method. + * @param target The recipient of the request. + * @param args The request arguments. + * @return The response. + * @throws IOException if the request failed. + */ + public Response request(final Method method, final URL target, final HttpArgs args) + throws IOException { + if (method == Method.GET) { + return get(target, args); + } + + throw new RuntimeException("Not implemented."); + } + + /** + * Create http request parameters from query. + * + * @param input The query inputs. + * @return The Http request arguments. + */ + public HttpArgs toArgs(final QueryInput input) { + final var args = new HttpArgs(); + if (input != null) { + args.setParams(input.getParams()); + args.setHeaders(input.getHeaders()); + } + + return args; + } + + /** + * Create http request parameters from query inputs and + * authentication information. + * + * @param input The query inputs. + * @param auth The authentication information. + * @return The http request arguments. + */ + public HttpArgs toArgs(final QueryInput input, final Pair auth) { + final var args = toArgs(input); + args.setAuth(auth); + + return args; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/ConnectorService.java b/src/main/java/io/dataspaceconnector/services/ids/ConnectorService.java new file mode 100644 index 000000000..06aaf7945 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/ConnectorService.java @@ -0,0 +1,203 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import de.fraunhofer.iais.eis.BaseConnector; +import de.fraunhofer.iais.eis.BaseConnectorImpl; +import de.fraunhofer.iais.eis.ConfigurationModelImpl; +import de.fraunhofer.iais.eis.DynamicAttributeToken; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceCatalog; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.services.ids.builder.IdsCatalogBuilder; +import io.dataspaceconnector.services.ids.builder.IdsResourceBuilder; +import io.dataspaceconnector.services.resources.CatalogService; +import io.dataspaceconnector.services.resources.OfferedResourceService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +/** + * This service offers different methods related to the connector configuration, like e.g. getting + * configuration properties or updating the configuration model. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class ConnectorService { + + /** + * The current connector configuration. + */ + private final @NonNull ConfigurationContainer configContainer; + + /** + * The token provider. + */ + private final @NonNull DapsTokenProvider tokenProvider; + + /** + * Service for persisted catalogs. + */ + private final @NonNull CatalogService catalogService; + + /** + * Service for ids views. + */ + private final @NonNull IdsCatalogBuilder catalogBuilder; + + /** + * Service for ids resources. + */ + private final @NonNull IdsResourceBuilder resourceBuilder; + + /** + * Service for offered resources. + */ + private final @NonNull OfferedResourceService offeredResourceService; + + /** + * Get a local copy of the current connector and extract its id. + * + * @return The connector id. + */ + public URI getConnectorId() { + final var connector = configContainer.getConnector(); + return connector.getId(); + } + + /** + * Get a local copy of the current connector and extract the outbound model version. + * + * @return The outbound model version. + */ + public String getOutboundModelVersion() { + final var connector = configContainer.getConnector(); + return connector.getOutboundModelVersion(); + } + + /** + * Get a local copy of the current connector and extract the inbound model versions. + * + * @return A list of supported model versions. + */ + public List getInboundModelVersion() { + final var connector = configContainer.getConnector(); + return connector.getInboundModelVersion(); + } + + /** + * Return current DAT. + * + * @return The connector's DAT. + */ + public DynamicAttributeToken getCurrentDat() { + return tokenProvider.getDAT(); + } + + /** + * Build a base connector object with all offered resources. + * + * @return The ids base connector object. + */ + public BaseConnector getConnectorWithOfferedResources() throws ConstraintViolationException { + // Get a local copy of the current connector. + final var connector = configContainer.getConnector(); + final var catalogs = getAllCatalogsWithOfferedResources(); + + // Create a connector with a list of offered resources. + final var connectorImpl = (BaseConnectorImpl) connector; + connectorImpl.setResourceCatalog((ArrayList) catalogs); + return connectorImpl; + } + + /** + * Build a base connector object without resources. + * + * @return The ids base connector object. + */ + public BaseConnector getConnectorWithoutResources() throws ConstraintViolationException { + // Get a local copy of the current connector. + final var connector = configContainer.getConnector(); + + // Create a connector without any resources. + final var connectorImpl = (BaseConnectorImpl) connector; + connectorImpl.setResourceCatalog(null); + return connectorImpl; + } + + /** + * Updates the connector object in the ids framework's config container. + * + * @throws ConfigurationUpdateException If the configuration could not be update. + */ + public void updateConfigModel() throws ConfigurationUpdateException { + try { + final var connector = getConnectorWithOfferedResources(); + final var configModel = (ConfigurationModelImpl) configContainer.getConfigModel(); + configModel.setConnectorDescription(connector); + + // Handled at a higher level. + configContainer.updateConfiguration(configModel); + } catch (ConstraintViolationException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to retrieve connector. [exception=({})]", e.getMessage(), e); + } + throw new ConfigurationUpdateException("Failed to retrieve connector.", e); + } + } + + /** + * Get all catalogs with offered resources. + * + * @return List of resource catalogs. + */ + private List getAllCatalogsWithOfferedResources() { + return catalogService.getAll(Pageable.unpaged()) + .stream() + .map(x -> catalogBuilder.create(x, 0)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Get offered resource by its id. + * + * @param resourceId The resource id. + * @return The ids resource. + */ + public Optional getOfferedResourceById(final URI resourceId) { + final var resource = offeredResourceService.getAll(Pageable.unpaged()) + .stream() + .filter(x -> resourceId.toString().contains(x.getId().toString())) + .findAny(); + + return resource.map(resourceBuilder::create); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/DeserializationService.java b/src/main/java/io/dataspaceconnector/services/ids/DeserializationService.java new file mode 100644 index 000000000..7050e8c5a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/DeserializationService.java @@ -0,0 +1,233 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import de.fraunhofer.iais.eis.ConfigurationModel; +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.InfrastructureComponent; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResponseMessage; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +/** + * Service class for ids object deserialization. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class DeserializationService { + + /** + * Service for ids serializations. + */ + private final @NonNull SerializerProvider serializerProvider; + + /** + * Deserialize string to ids configuration model. + * + * @param config The configuration model string. + * @return The ids configuration model. + * @throws IllegalArgumentException If deserialization fails. + */ + public ConfigurationModel getConfigurationModel(final String config) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(config, ConfigurationModel.class); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize config model. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize input.", e); + } + } + + /** + * Deserialize string to ids infrastructure component. + * + * @param component The infrastructure component string. + * @return The ids object. + * @throws IllegalArgumentException If deserialization fails. + */ + public InfrastructureComponent getInfrastructureComponent(final String component) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(component, + InfrastructureComponent.class); + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Could not deserialize component. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize input.", e); + } + } + + /** + * Deserialize string to ids resource. + * + * @param resource The resource string. + * @return The ids object. + * @throws IllegalArgumentException If deserialization fails. + */ + public Resource getResource(final String resource) throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(resource, Resource.class); + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Could not deserialize resource. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize input.", e); + } + } + + /** + * Returns the ids header of an http multipart response if the header is of type + * ResponseMessage. + * + * @param response An ids response message. + * @return The response message. + * @throws IllegalArgumentException If deserialization fails. + */ + public ResponseMessage getResponseMessage(final String response) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(response, ResponseMessage.class); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize response message. [exception=({})]", + e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize response message.", e); + } + } + + /** + * Deserialize string to ids message. + * + * @param response An ids message. + * @return The message. + * @throws IllegalArgumentException If deserialization fails. + */ + public Message getMessage(final String response) throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(response, Message.class); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize message. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize message.", e); + } + } + + /** + * Deserialize string to ids rule. + * + * @param policy The policy string. + * @return The ids rule. + * @throws IllegalArgumentException If deserialization fails. + */ + public Rule getRule(final String policy) throws IllegalArgumentException { + return getRule(policy, Rule.class); + } + + /** + * Deserialize string to ids object of type rule. + * + * @param policy The policy string. + * @param tClass The Infomodel class. + * @param The class type. + * @return An ids object of type rule. + * @throws IllegalArgumentException If deserialization fails. + */ + public T getRule(final String policy, final Class tClass) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(policy, tClass); + } catch (IOException exception) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize rule. [exception=({})]", exception.getMessage()); + } + throw new IllegalArgumentException("Could not deserialize rule.", exception); + } + } + + /** + * Check if a string is of type ids rule. + * + * @param policy The policy string. + * @param tClass The Infomodel class. + * @param The class type. + * @return False if the matching fails, true if not. + */ + public boolean isRuleType(final String policy, final Class tClass) { + var isType = false; + try { + serializerProvider.getSerializer().deserialize(policy, tClass); + isType = true; + } catch (IOException ignore) { + // Intentionally empty + } + + return isType; + } + + /** + * Deserialize string to ids contract agreement. + * + * @param contract The contract string. + * @return The ids contract agreement. + * @throws IllegalArgumentException If deserialization fails. + */ + public ContractAgreement getContractAgreement(final String contract) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(contract, + ContractAgreement.class); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize agreement. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize contract agreement.", e); + } + } + + /** + * Deserialize string to ids contract request. + * + * @param contract The contract string. + * @return The ids contract request. + * @throws IllegalArgumentException If deserialization fails. + */ + public ContractRequest getContractRequest(final String contract) + throws IllegalArgumentException { + try { + return serializerProvider.getSerializer().deserialize(contract, ContractRequest.class); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Could not deserialize request. [exception=({})]", e.getMessage(), e); + } + throw new IllegalArgumentException("Could not deserialize contract request.", e); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/AbstractIdsBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/AbstractIdsBuilder.java new file mode 100644 index 000000000..267cf133a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/AbstractIdsBuilder.java @@ -0,0 +1,195 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.utils.SelfLinkHelper; +import io.dataspaceconnector.utils.Utils; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * The base class for constructing an ids object from DSC objects. + * + * @param The type of the DSC object. + * @param The type of the ids object. + */ +@Log4j2 +@NoArgsConstructor +public abstract class AbstractIdsBuilder { + + /** + * The default depth the builder will follow dependencies. + */ + public static final int DEFAULT_DEPTH = -1; + + /** + * The max depth when searching for the setProperty method in ids objects. + */ + private static final int MAX_DEPTH = 3; + + /** + * Convert an DSC object to an ids object. The default depth will be used to determine the + * when to stop following dependencies. + * + * @param entity The entity to be converted. + * @return The ids object. + */ + public X create(final T entity) throws ConstraintViolationException { + return create(entity, DEFAULT_DEPTH); + } + + /** + * Convert an DSC object to an ids object. + * + * @param entity The entity to be converted. + * @param maxDepth The depth determines when to stop following dependencies. Set this value to a + * negative number to follow all dependencies. + * @return The ids object. + */ + public X create(final T entity, final int maxDepth) throws ConstraintViolationException { + return create(entity, getBaseUri(), 0, maxDepth); + } + + private X create(final T entity, final URI baseUri, final int currentDepth, + final int maxDepth) throws ConstraintViolationException { + final var resource = createInternal(entity, baseUri, currentDepth, maxDepth); + if (resource != null) { + return addAdditionals(resource, entity.getAdditional()); + } else { + return null; + } + } + + /** + * This is the type specific call for converting an DSC object to an Infomodel object. The + * additional field will be set automatically. + * + * @param entity The entity to be converted. + * @param baseUri The hostname of the system. + * @param currentDepth The current distance to the original call. + * @param maxDepth The max depth to the original call. + * @return The ids object. + */ + protected abstract X createInternal(T entity, URI baseUri, int currentDepth, int maxDepth) + throws ConstraintViolationException; + + private URI getBaseUri() { + return URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().toUriString()); + } + + /** + * Use this function to construct the absolute path to this entity. + * + * @param entity The entity. + * @param baseUri The hostname. + * @param The entity type. + * @return The absolute path to this entity. + */ + protected URI getAbsoluteSelfLink(final X entity, + final URI baseUri) { + var uri = SelfLinkHelper.getSelfLink(entity); + + if (!uri.isAbsolute()) { + uri = URI.create(baseUri.toString() + uri); + } + + return uri; + } + + private static boolean shouldGenerate(final int currentDepth, final int maxDepth) { + return currentDepth <= maxDepth || maxDepth < 0; + } + + // NOTE: The type of ArrayList is used because the ids object expects ArrayList for some + // unknown reason. By changing the return type from List to ArrayList it is more convenient + // to use since no typecast is required. + + /** + * Batch call of create. Use this call for building an object's dependencies. This function + * increments the currentDepth. + * + * @param builder The builder applied to all objects. + * @param entityList The entities that need to be converted. + * @param baseUri The hostname of the system. + * @param currentDepth The current distance to the original call. + * @param maxDepth The distance to the original call. + * @param The type of the DSC entity. + * @param The type of the ids entity. + * @return The converted ids objects. Null if the distance is to far to the original call. + */ + protected Optional> create( + final AbstractIdsBuilder builder, final List entityList, final URI baseUri, + final int currentDepth, final int maxDepth) throws ConstraintViolationException { + final int nextDepth = currentDepth + 1; + + return !shouldGenerate(nextDepth, maxDepth) ? Optional.empty() + : Optional.of(new ArrayList<>(Utils.toStream(entityList) + .map(r -> builder + .create(r, baseUri, nextDepth, maxDepth)) + .filter(Objects::nonNull) + .collect(Collectors.toList()))); + } + + private X addAdditionals(final X idsObject, final Map additional) { + // NOTE: The Infomodel lib has setProperty on all classes, but the method is implemented + // individually... + try { + final var setPropertyMethod = findAdditionalMethod(idsObject); + for (final var entry : additional.entrySet()) { + setPropertyMethod.invoke(idsObject, entry.getKey(), entry.getValue()); + } + + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to set additional fields. [exception=({})]", e.getMessage(), e); + } + } + + return idsObject; + } + + private Method findAdditionalMethod(final X idsResource) throws NoSuchMethodException { + // NOTE: The Infomodel lib has setProperty on all classes, but some of them are implemented + // higher up the inheritance chain. + // If the setProperty method has a different signature null is returned. + var tClass = idsResource.getClass(); + for (int i = 0; i < MAX_DEPTH; i++) { + try { + return tClass.getMethod("setProperty", String.class, Object.class); + } catch (NoSuchMethodException ignore) { + // Intentionally empty + } + if (i < MAX_DEPTH - 1) { + tClass = tClass.getSuperclass(); + } + } + + throw new NoSuchMethodException(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsArtifactBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsArtifactBuilder.java new file mode 100644 index 000000000..c28f49416 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsArtifactBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import java.math.BigInteger; +import java.net.URI; + +import de.fraunhofer.iais.eis.ArtifactBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * Converts DSC Artifacts to Infomodel Artifacts. + */ +@Component +@NoArgsConstructor +public final class IdsArtifactBuilder + extends AbstractIdsBuilder { + + @Override + protected de.fraunhofer.iais.eis.Artifact createInternal(final Artifact artifact, + final URI baseUri, + final int currentDepth, + final int maxDepth) + throws ConstraintViolationException { + // Prepare artifact attributes. + final var created = IdsUtils.getGregorianOf(artifact + .getCreationDate()); + + return new ArtifactBuilder(getAbsoluteSelfLink(artifact, baseUri)) + ._byteSize_(BigInteger.valueOf(artifact.getByteSize())) + ._checkSum_(BigInteger.valueOf(artifact.getCheckSum()).toString()) + ._creationDate_(created) + ._fileName_(artifact.getTitle()) + .build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsCatalogBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsCatalogBuilder.java new file mode 100644 index 000000000..912046a91 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsCatalogBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.ResourceCatalog; +import de.fraunhofer.iais.eis.ResourceCatalogBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.OfferedResource; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * Converts DSC artifacts to ids artifacts. + */ +@Component +@RequiredArgsConstructor +public final class IdsCatalogBuilder extends AbstractIdsBuilder { + + /** + * The builder for ids resource (from offered resource). + */ + private final @NonNull IdsResourceBuilder resourceBuilder; + + @Override + protected ResourceCatalog createInternal(final Catalog catalog, final URI baseUri, + final int currentDepth, final int maxDepth) + throws ConstraintViolationException { + // Build children. + final var resources = create(resourceBuilder, + catalog.getOfferedResources(), baseUri, currentDepth, maxDepth); + + final var builder = new ResourceCatalogBuilder(getAbsoluteSelfLink(catalog, baseUri)); + resources.ifPresent(builder::_offeredResource_); + + return builder.build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsContractBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsContractBuilder.java new file mode 100644 index 000000000..410706105 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsContractBuilder.java @@ -0,0 +1,148 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.ContractOffer; +import de.fraunhofer.iais.eis.ContractOfferBuilder; +import de.fraunhofer.iais.eis.Duty; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.Prohibition; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.isst.ids.framework.util.IDSUtils; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.utils.IdsUtils; +import io.dataspaceconnector.utils.Utils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Converts DSC contracts to ids contract offers. + */ +@Component +@RequiredArgsConstructor +public final class IdsContractBuilder extends AbstractIdsBuilder { + + /** + * The builder for ids permission. + */ + private final @NonNull IdsPermissionBuilder permBuilder; + + /** + * The builder for ids prohibition. + */ + private final @NonNull IdsProhibitionBuilder prohBuilder; + + /** + * The builder for ids duty. + */ + private final @NonNull IdsDutyBuilder dutyBuilder; + + /** + * The service for deserializing strings to ids rules. + */ + private final @NonNull DeserializationService deserializer; + + @Override + protected ContractOffer createInternal(final Contract contract, final URI baseUri, + final int currentDepth, final int maxDepth) + throws ConstraintViolationException { + // Build children. + final var permissions = + create(permBuilder, onlyPermissions(contract.getRules()), baseUri, + currentDepth, maxDepth); + final var prohibitions = + create(prohBuilder, onlyProhibitions(contract.getRules()), baseUri, + currentDepth, maxDepth); + final var duties = + create(dutyBuilder, onlyDuties(contract.getRules()), baseUri, currentDepth, + maxDepth); + + // Build contract only if at least one rule is present. + if (permissions.isEmpty() && prohibitions.isEmpty() && duties.isEmpty()) { + return null; + } + + boolean permissionsEmpty = false; + boolean prohibitionsEmpty = false; + boolean dutiesEmpty = false; + + if (permissions.isPresent()) { + permissionsEmpty = permissions.get().isEmpty(); + } + + if (prohibitions.isPresent()) { + prohibitionsEmpty = prohibitions.get().isEmpty(); + } + + if (duties.isPresent()) { + dutiesEmpty = duties.get().isEmpty(); + } + + if (permissionsEmpty && prohibitionsEmpty && dutiesEmpty) { + return null; + } + + // Prepare contract attributes. + final var start = IdsUtils.getGregorianOf(contract.getStart()); + final var end = IdsUtils.getGregorianOf(contract.getEnd()); + final var consumer = contract.getConsumer(); + final var provider = contract.getProvider(); + + final var builder = new ContractOfferBuilder(getAbsoluteSelfLink(contract, baseUri)) + ._contractStart_(start) + ._contractEnd_(end) + ._contractDate_(IDSUtils.getGregorianNow()) + ._consumer_(consumer) + ._provider_(provider); + + permissions.ifPresent(builder::_permission_); + prohibitions.ifPresent(builder::_prohibition_); + duties.ifPresent(builder::_obligation_); + + return builder.build(); + } + + private List onlyPermissions(final List rules) { + return Utils.toStream(rules).filter(this::isPermission).collect(Collectors.toList()); + } + + private List onlyProhibitions(final List rules) { + return Utils.toStream(rules).filter(this::isProhibition).collect(Collectors.toList()); + } + + private List onlyDuties(final List rules) { + return Utils.toStream(rules).filter(this::isDuty).collect(Collectors.toList()); + } + + private boolean isPermission(final ContractRule rule) { + return deserializer.isRuleType(rule.getValue(), Permission.class); + } + + private boolean isProhibition(final ContractRule rule) { + return deserializer.isRuleType(rule.getValue(), Prohibition.class); + } + + private boolean isDuty(final ContractRule rule) { + return deserializer.isRuleType(rule.getValue(), Duty.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsDutyBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsDutyBuilder.java new file mode 100644 index 000000000..bfd78afd3 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsDutyBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.Duty; +import io.dataspaceconnector.services.ids.DeserializationService; +import lombok.NonNull; +import org.springframework.stereotype.Component; + +/** + * Converts DSC rule to ids duty. + */ +@Component +public class IdsDutyBuilder extends IdsRuleBuilder { + IdsDutyBuilder(@NonNull final DeserializationService deserializer) { + super(deserializer, Duty.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsPermissionBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsPermissionBuilder.java new file mode 100644 index 000000000..5bcee335a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsPermissionBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.Permission; +import io.dataspaceconnector.services.ids.DeserializationService; +import lombok.NonNull; +import org.springframework.stereotype.Component; + +/** + * Converts DSC rule to ids permission. + */ +@Component +public class IdsPermissionBuilder extends IdsRuleBuilder { + IdsPermissionBuilder(@NonNull final DeserializationService deserializer) { + super(deserializer, Permission.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsProhibitionBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsProhibitionBuilder.java new file mode 100644 index 000000000..9fa5c2cea --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsProhibitionBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.Prohibition; +import io.dataspaceconnector.services.ids.DeserializationService; +import lombok.NonNull; +import org.springframework.stereotype.Component; + +/** + * Converts DSC rule to ids prohibition. + */ +@Component +public class IdsProhibitionBuilder extends IdsRuleBuilder { + IdsProhibitionBuilder(@NonNull final DeserializationService deserializer) { + super(deserializer, Prohibition.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRepresentationBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRepresentationBuilder.java new file mode 100644 index 000000000..166d0d43e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRepresentationBuilder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.IANAMediaTypeBuilder; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * Converts DSC representation to ids representation. + */ +@Component +@RequiredArgsConstructor +public final class IdsRepresentationBuilder + extends AbstractIdsBuilder { + + /** + * The builder for Infomodel Artifacts. + */ + private final @NonNull IdsArtifactBuilder artifactBuilder; + + @Override + protected de.fraunhofer.iais.eis.Representation createInternal( + final Representation representation, + final URI baseUri, final int currentDepth, + final int maxDepth) throws ConstraintViolationException { + // Build children. + final var artifacts = + create(artifactBuilder, representation.getArtifacts(), baseUri, currentDepth, + maxDepth); + + // Build representation only if at least one artifact is present. + if (artifacts.isEmpty() || artifacts.get().isEmpty()) { + return null; + } + + // Prepare representation attributes. + final var modified = IdsUtils.getGregorianOf(representation + .getModificationDate()); + final var created = IdsUtils.getGregorianOf(representation + .getCreationDate()); + final var language = IdsUtils.getLanguage(representation.getLanguage()); + final var mediaType = + new IANAMediaTypeBuilder()._filenameExtension_(representation.getMediaType()) + .build(); + final var standard = URI.create(representation.getStandard()); + + final var builder = new RepresentationBuilder(getAbsoluteSelfLink(representation, baseUri)) + ._created_(created) + ._language_(language) + ._mediaType_(mediaType) + ._modified_(modified) + ._representationStandard_(standard); + + artifacts.ifPresent(builder::_instance_); + + return builder.build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsResourceBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsResourceBuilder.java new file mode 100644 index 000000000..da8a897a4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsResourceBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.Resource; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.util.ArrayList; + +/** + * Converts DSC resource to ids resource. + * + * @param The resource type. + */ +@Component +@RequiredArgsConstructor +public final class IdsResourceBuilder + extends AbstractIdsBuilder { + + /** + * The builder for ids representation. + */ + private final @NonNull IdsRepresentationBuilder repBuilder; + + /** + * The builder for ids contract offer. + */ + private final @NonNull IdsContractBuilder contractBuilder; + + @Override + protected de.fraunhofer.iais.eis.Resource createInternal(final Resource resource, + final URI baseUri, + final int currentDepth, + final int maxDepth) + throws ConstraintViolationException { + // Build children. + final var representations = + create(repBuilder, resource.getRepresentations(), baseUri, currentDepth, + maxDepth); + final var contracts = + create(contractBuilder, resource.getContracts(), baseUri, currentDepth, maxDepth); + + // Prepare resource attributes. + final var selfLink = getAbsoluteSelfLink(resource, baseUri); + final var created = IdsUtils.getGregorianOf(resource.getCreationDate()); + final var modified = IdsUtils.getGregorianOf(resource.getModificationDate()); + final var description = resource.getDescription(); + final var language = resource.getLanguage(); + final var idsLanguage = IdsUtils.getLanguage(resource.getLanguage()); + final var keywords = IdsUtils.getKeywordsAsTypedLiteral(resource.getKeywords(), + language); + final var license = resource.getLicence(); + final var publisher = resource.getPublisher(); + final var sovereign = resource.getSovereign(); + final var title = resource.getTitle(); + final var version = resource.getVersion(); + final var endpointDocs = resource.getEndpointDocumentation(); + + final var endpoint = new ConnectorEndpointBuilder() + ._accessURL_(selfLink) + ._endpointDocumentation_(Util.asList(endpointDocs)) + .build(); + + // Build resource only if at least one representation and one contract is present. + if (representations.isEmpty() || contracts.isEmpty() || representations.get().isEmpty() + || contracts.get().isEmpty()) { + return null; + } + + final var builder = new ResourceBuilder(selfLink) +// ._accrualPeriodicity_() +// ._assetRefinement_() +// ._contentType_() + ._created_(created) + ._description_(Util.asList(new TypedLiteral(description, language))) + ._language_(Util.asList(idsLanguage)) + ._keyword_((ArrayList) keywords) + ._modified_(modified) + ._publisher_(publisher) + ._resourceEndpoint_(Util.asList(endpoint)) + ._sovereign_(sovereign) +// ._spatialCoverage_() +// ._shapesGraph_() + ._standardLicense_(license) +// ._temporalCoverage_() +// ._temporalResolution_() + ._title_(Util.asList(new TypedLiteral(title, language))) + ._version_(String.valueOf(version)); + + representations.ifPresent(builder::_representation_); + contracts.ifPresent(builder::_contractOffer_); + + return builder.build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRuleBuilder.java b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRuleBuilder.java new file mode 100644 index 000000000..0de5780bb --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/IdsRuleBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.builder; + +import java.net.URI; + +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.services.ids.DeserializationService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * The base class for constructing an ids rule from a DSC rule. + * + * @param The ids rule type. + */ +@RequiredArgsConstructor +public class IdsRuleBuilder extends AbstractIdsBuilder { + + /** + * The service for deserializing strings to ids rules. + */ + private final @NonNull DeserializationService deserializer; + + /** + * The type of the rule to be build. Needed for the deserializer. + */ + private final @NonNull Class ruleType; + + @Override + protected final T createInternal(final ContractRule rule, final URI baseUri, + final int currentDepth, final int maxDepth) + throws ConstraintViolationException { + final var idsRule = deserializer.getRule(rule.getValue()); + final var selfLink = getAbsoluteSelfLink(rule, baseUri); + var newRule = rule.getValue(); + + // Note: Infomodel deserializer sets autogen ID, when ID is missing in original rule value. + // If autogen ID not present in original rule value, it's equal to rule not having ID + if (idsRule.getId() == null || rule.getValue().indexOf(idsRule.getId().toString()) == -1) { + // No id has been set for this rule. Thus, no references can be found. + // Inject the real id. + newRule = newRule.substring(0, newRule.indexOf("{") + 1) + + "\"@id\": \"" + + selfLink + "\"," + + newRule + .substring(newRule.indexOf("{") + 1); + } else { + // The id has been set, there may be references. + // Search for the id and replace everywhere. + newRule = newRule.replace(idsRule.getId().toString(), selfLink.toString()); + + } + + return deserializer.getRule(newRule, ruleType); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/builder/package-info.java b/src/main/java/io/dataspaceconnector/services/ids/builder/package-info.java new file mode 100644 index 000000000..7dc1704cf --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/builder/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Builder construct IDS Objects from Dataspace Connector entities. + */ +package io.dataspaceconnector.services.ids.builder; diff --git a/src/main/java/io/dataspaceconnector/services/ids/package-info.java b/src/main/java/io/dataspaceconnector/services/ids/package-info.java new file mode 100644 index 000000000..63c8382dd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Services for performing IDS actions with the Dataspace Connector. + */ +package io.dataspaceconnector.services.ids; diff --git a/src/main/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdater.java b/src/main/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdater.java new file mode 100644 index 000000000..b61dd79b4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdater.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.utils.MappingUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * This component is responsible for updating artifacts when an IDS artifact is provided. + */ +@Component +@RequiredArgsConstructor +public class ArtifactUpdater + implements InfomodelUpdater { + /** + * Service for artifacts. + */ + private final @NonNull ArtifactService artifactService; + + @Override + public final Artifact update(final de.fraunhofer.iais.eis.Artifact entity) + throws ResourceNotFoundException { + final var entityId = artifactService.identifyByRemoteId(entity.getId()); + if (entityId.isEmpty()) { + throw new ResourceNotFoundException(entity.getId().toString()); + } + + final var artifact = artifactService.get(entityId.get()); + final var template = MappingUtils.fromIdsArtifact( + entity, artifact.isAutomatedDownload(), artifact.getRemoteAddress()); + return artifactService.update(entityId.get(), template.getDesc()); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/updater/InfomodelUpdater.java b/src/main/java/io/dataspaceconnector/services/ids/updater/InfomodelUpdater.java new file mode 100644 index 000000000..aa82a9233 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/updater/InfomodelUpdater.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.AbstractEntity; + +/** + * Updates an DSC object by providing an IDS object. + * @param The IDS object type. + * @param The DSC object type. + */ +public interface InfomodelUpdater { + /** + * Update an entity that is known to the consumer. + * @param entity The ids object. + * @return The updated dsc object. + */ + O update(I entity) throws ResourceNotFoundException; +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdater.java b/src/main/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdater.java new file mode 100644 index 000000000..032aa3029 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdater.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import de.fraunhofer.iais.eis.Representation; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.services.resources.RepresentationService; +import io.dataspaceconnector.utils.MappingUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * Updates a dsc representation based on a provided ids representation. + */ +@Component +@RequiredArgsConstructor +public final class RepresentationUpdater implements InfomodelUpdater { + + /** + * Service for representations. + */ + private final @NonNull RepresentationService representationService; + + /** + * {@inheritDoc} + */ + @Override + public io.dataspaceconnector.model.Representation update( + final Representation entity) throws ResourceNotFoundException { + final var entityId = representationService.identifyByRemoteId(entity.getId()); + if (entityId.isEmpty()) { + throw new ResourceNotFoundException(entity.getId().toString()); + } + + final var template = MappingUtils.fromIdsRepresentation(entity); + return representationService.update(entityId.get(), template.getDesc()); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdater.java b/src/main/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdater.java new file mode 100644 index 000000000..bb1dd9561 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdater.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.services.resources.RequestedResourceService; +import io.dataspaceconnector.utils.MappingUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * Updates a dsc requested resource based on a provided ids resource. + */ +@Component +@RequiredArgsConstructor +public final class RequestedResourceUpdater + implements InfomodelUpdater { + + /** + * Service for requested resources. + */ + private final @NonNull RequestedResourceService requestedResourceService; + + /** + * {@inheritDoc} + */ + @Override + public RequestedResource update(final de.fraunhofer.iais.eis.Resource entity) + throws ResourceNotFoundException { + final var entityId = requestedResourceService.identifyByRemoteId(entity.getId()); + if (entityId.isEmpty()) { + throw new ResourceNotFoundException(entity.getId().toString()); + } + + final var template = MappingUtils.fromIdsResource(entity); + return requestedResourceService.update(entityId.get(), template.getDesc()); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/ids/updater/package-info.java b/src/main/java/io/dataspaceconnector/services/ids/updater/package-info.java new file mode 100644 index 000000000..14cd17a0d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/ids/updater/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Update DSC entities based on IDS Objects. + */ +package io.dataspaceconnector.services.ids.updater; diff --git a/src/main/java/io/dataspaceconnector/services/messages/MessageResponseService.java b/src/main/java/io/dataspaceconnector/services/messages/MessageResponseService.java new file mode 100644 index 000000000..6271aa6e8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/MessageResponseService.java @@ -0,0 +1,640 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.RejectionReason; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.InvalidInputException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.exceptions.SelfLinkCreationException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * This class handles message responses. + */ +@Log4j2 +@Component +@RequiredArgsConstructor +public class MessageResponseService { + + /** + * Service for the current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Handles thrown {@link MessageEmptyException}. + * + * @param exception Exception that was thrown when checking if the message is null. + * @return A message response. + * @throws IllegalArgumentException if exception is null. + */ + public MessageResponse handleMessageEmptyException(final MessageEmptyException exception) { + Utils.requireNonNull(exception, ErrorMessages.EXCEPTION_NULL); + + if (log.isDebugEnabled()) { + log.debug("Cannot respond when there is no request. [exception=({})]", + exception.getMessage(), exception); + } + + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + exception.getMessage(), connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown {@link VersionNotSupportedException}. + * + * @param exception Exception that was thrown when checking the Information Model version. + * @param version Information Model version of incoming message. + * @return A message response. + * @throws IllegalArgumentException if exception is null. + */ + public MessageResponse handleInfoModelNotSupportedException( + final VersionNotSupportedException exception, final String version) { + Utils.requireNonNull(exception, ErrorMessages.EXCEPTION_NULL); + + if (log.isDebugEnabled()) { + log.debug("Information Model version of requesting connector is not supported. " + + "[version=({}), exception=({})]", version, exception.getMessage(), exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.VERSION_NOT_SUPPORTED, + exception.getMessage(), connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown exceptions when building the response message. + * + * @param exception Exception that was thrown when building the response message. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + * @throws IllegalArgumentException if exception is null. + */ + public MessageResponse handleResponseMessageBuilderException(final Exception exception, + final URI issuerConnector, + final URI messageId) { + Utils.requireNonNull(exception, ErrorMessages.EXCEPTION_NULL); + + if (log.isWarnEnabled()) { + log.warn("Failed to convert ids object to string. [exception=({}), " + + "issuer=({}), messageId=({})]", exception.getMessage(), + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Response could not be constructed.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown {@link PolicyRestrictionException}. + * + * @param exception Exception that was thrown when checking for data access. + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handlePolicyRestrictionException( + final PolicyRestrictionException exception, final URI requestedArtifact, + final URI transferContract, final URI issuerConnector, final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Policy restriction detected. [exception=({}), artifact=({}), " + + "contract=({}), issuer=({}), messageId=({})]", exception.getMessage(), + requestedArtifact, transferContract, issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_AUTHORIZED, + "Policy restriction detected." + exception.getMessage(), + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown {@link IllegalArgumentException}. + * + * @param exception Exception that was thrown when deserializing a message's payload. + * @param payload The message's payload. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleIllegalArgumentException(final IllegalArgumentException exception, + final String payload, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Could not parse message payload. [exception=({}), payload=({}), " + + "issuer=({}), messageId=({})]", exception.getMessage(), payload, + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Could not parse message payload.", connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown exception while finding the requested element. + * + * @param exception Exception that was thrown when trying to sendMessage the message. + * @param requestedElement The requested element. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleResourceNotFoundException(final Exception exception, + final URI requestedElement, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Element not found. [exception=({}), resourceId=({}), issuer=({}), " + + "messageId=({})]", exception.getMessage(), requestedElement, + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, String.format( + "The requested element %s could not be found.", requestedElement), + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handles thrown exceptions in processing a message's payload. + * + * @param exception Exception that was thrown while reading a message's payload. + * @param messageId The id of the incoming message. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @return A message response. + */ + public MessageResponse handleMessagePayloadException(final Exception exception, + final URI messageId, + final URI issuerConnector) { + if (log.isDebugEnabled()) { + log.debug("Failed to read payload. [exception=({}), messageId=({}), " + + "issuer=({})]", exception.getMessage(), messageId, issuerConnector, + exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + exception.getMessage(), + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing rules in contract request message. + * + * @param request The contract request. + * @param messageId The id of the incoming message. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingRules(final ContractRequest request, + final URI messageId, + final URI issuerConnector) { + if (log.isDebugEnabled()) { + log.debug("No rules found. [request=({}), messageId=({}), issuer=({})]", + request, messageId, issuerConnector); + } + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "Missing rules in contract request.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing target in rules of a contract request. + * + * @param request The contract request. + * @param messageId The id of the incoming message. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingTargetInRules(final ContractRequest request, + final URI messageId, + final URI issuerConnector) { + if (log.isDebugEnabled()) { + log.debug("No targets found. [request=({}), messageId=({}), issuer=({})]", + request, messageId, issuerConnector); + } + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "Missing targets in rules of contract request.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing contract offers matching the contract request targets. + * + * @param request The contract request. + * @param messageId The id of the incoming message. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingContractOffers(final ContractRequest request, + final URI messageId, + final URI issuerConnector) { + if (log.isDebugEnabled()) { + log.debug("No contract offers found. [request=({}), messageId=({}), " + + "issuer=({})]", request, messageId, issuerConnector); + } + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Could not find any matching contract offers for your request.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle global message processing failed. + * + * @param exception Exception that was thrown while processing a request message. + * @param payload The message's payload. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMessageProcessingFailed(final Exception exception, + final String payload, + final URI issuerConnector, + final URI messageId) { + if (log.isWarnEnabled()) { + log.warn("Could not process request message. [exception=({}), payload=({}), " + + "issuer=({}), messageId=({})]", exception.getMessage(), payload, + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Could not process request message. " + exception.getMessage(), + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle global message processing failed. + * + * @param exception Exception that was thrown while processing a request message. + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMessageProcessingFailed(final Exception exception, + final URI requestedArtifact, + final URI transferContract, + final URI issuerConnector, + final URI messageId) { + if (log.isWarnEnabled()) { + log.warn("Could not process request message. [exception=({}), artifact=({}), " + + "contract=({}), issuer=({}), messageId=({})]", exception.getMessage(), + requestedArtifact, transferContract, issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Could not process request message. " + exception.getMessage(), + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle contract exception. + * + * @param exception The exception that was thrown when validating the contracts. + * @param payload The message's payload. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleContractException(final ContractException exception, + final String payload, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Invalid contract agreement request. [exception=({}), payload=({}), " + + "issuer=({}), messageId=({})]", exception, payload, issuerConnector, + messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "This agreement does not match the one handled out before.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle exceptions when saving a contract agreement. + * + * @param exception Exception that was thrown while storing a contract agreement. + * @param agreement The contract agreement. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleAgreementPersistenceException(final Exception exception, + final ContractAgreement agreement, + final URI issuerConnector, + final URI messageId) { + if (log.isWarnEnabled()) { + log.warn("Could not store contract agreement. [exception=({}), " + + "agreement=({}), issuer=({}), messageId=({})]", + exception.getMessage(), agreement, issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Could not store contract agreement.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing transfer contract in request message. + * + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingTransferContract(final URI requestedArtifact, + final URI transferContract, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Missing transfer contract. [artifact=({}), contract=({}), " + + "issuer=({}), messageId=({})]", requestedArtifact, transferContract, + issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Missing transfer contract.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle {@link ContractException} because of invalid transfer contract for requested artifact. + * + * @param exception Exception that was thrown while checking the transfer contract. + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleInvalidTransferContract(final ContractException exception, + final URI requestedArtifact, + final URI transferContract, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Invalid transfer contract. [exception=({}), artifact=({}), " + + "contract=({}), issuer=({}), messageId=({})]", exception.getMessage(), + requestedArtifact, transferContract, issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Invalid transfer contract for requested artifact.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing requested artifact in request message. + * + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingRequestedArtifact(final URI requestedArtifact, + final URI transferContract, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Missing requested artifact. [artifact=({}), contract=({}), " + + "issuer=({}), messageId=({})]", requestedArtifact, transferContract, + issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Missing requested artifact.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle {@link InvalidInputException} because of an invalid query input in message payload. + * + * @param exception Exception that was thrown while reading the query input. + * @param requestedArtifact The requested artifact. + * @param transferContract The transfer contract id. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleInvalidQueryInput(final InvalidInputException exception, + final URI requestedArtifact, + final URI transferContract, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Invalid query input. [exception=({}), artifact=({}), contract=({}), " + + "issuer=({}), messageId=({})]", exception.getMessage(), + requestedArtifact, transferContract, issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Invalid query input.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle exceptions when retrieving the data. + * + * @param exception Exception that was thrown while getting the data. + * @param requestedArtifact The requested artifact. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleFailedToRetrieveData(final Exception exception, + final URI requestedArtifact, + final URI issuerConnector, + final URI messageId) { + if (log.isWarnEnabled()) { + log.warn("Failed to load data. [exception=({}), artifact=({}), issuer=({}), " + + "messageId=({})]", exception.getMessage(), requestedArtifact, + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Could not retrieve data.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing affected resource in request message. + * + * @param affectedResource The affected resource. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingAffectedResource(final URI affectedResource, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Missing affected resource. [resource=({}), issuer=({}), " + + "messageId=({})]", affectedResource, issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Missing affected resource.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle missing payload content in request message. + * + * @param affectedResource The affected resource. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMissingPayload(final URI affectedResource, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Missing resource in payload. [resource=({}), issuer=({}), " + + "messageId=({})]", affectedResource, issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Missing resource in payload.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle mismatch in affected resource and resource id of the incoming payload. + * + * @param resourceId The id of the resource in the payload. + * @param affectedResource The affected resource. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleInvalidAffectedResource(final URI resourceId, + final URI affectedResource, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Affected resource does not match the resource id. [resource=({}), " + + "affectedResource=({}), issuer=({}), messageId=({})]", resourceId, + affectedResource, issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Affected resource does not match the resource id.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle malformed rules in contract request. + * + * @param exception Exception that was thrown while checking the contract rules. + * @param payload The message's payload. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleMalformedRules(final IllegalArgumentException exception, + final String payload, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("Could not parse message payload. [exception=({}), payload=({}), " + + "issuer=({}), messageId=({})]", exception.getMessage(), payload, + issuerConnector, messageId, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.MALFORMED_MESSAGE, + "Invalid rules in message payload.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Handle exception when creating self links for the requested element and its children. + * + * @param exception Exception that was thrown when the self links could not be created. + * @param requestedElement The requested element that could not be constructed. + * @return A message response. + */ + public MessageResponse handleSelfLinkCreationException( + final SelfLinkCreationException exception, final URI requestedElement) { + if (log.isDebugEnabled()) { + log.debug("Could not construct self links for requested element and its " + + "children. [exception=({}), requestedElement=({})]", + exception.getMessage(), requestedElement, exception); + } + return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Internal error when constructing requested element.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } + + /** + * Respond with error if the transfer contract has not yet been confirmed. + * + * @param storedAgreement The contract agreement. + * @param issuerConnector The issuer connector extracted from the incoming message. + * @param messageId The id of the incoming message. + * @return A message response. + */ + public MessageResponse handleUnconfirmedAgreement(final Agreement storedAgreement, + final URI issuerConnector, + final URI messageId) { + if (log.isDebugEnabled()) { + log.debug("This the transfer contract has not yet been confirmed. " + + "[agreement=({}), issuer=({}), messageId=({})]", storedAgreement, + issuerConnector, messageId); + } + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "This the transfer contract has not yet been confirmed.", + connectorService.getConnectorId(), + connectorService.getOutboundModelVersion()); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandler.java new file mode 100644 index 000000000..a93f775d0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandler.java @@ -0,0 +1,230 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ArtifactRequestMessageImpl; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.InvalidInputException; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.model.messages.ArtifactResponseMessageDesc; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.ArtifactResponseService; +import io.dataspaceconnector.services.usagecontrol.ContractManager; +import io.dataspaceconnector.services.usagecontrol.DataProvisionVerifier; +import io.dataspaceconnector.services.usagecontrol.VerificationInput; +import io.dataspaceconnector.services.usagecontrol.VerificationResult; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MessageUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; + +import java.io.IOException; +import java.net.URI; + +/** + * This @{@link ArtifactRequestHandler} handles all incoming messages that have a + * {@link de.fraunhofer.iais.eis.ArtifactRequestMessageImpl} as part one in the multipart message. + * This header must have the correct '@type' reference as defined in the + * {@link de.fraunhofer.iais.eis.ArtifactRequestMessageImpl} JsonTypeName annotation. + */ +@Component +@Log4j2 +@SupportedMessageType(ArtifactRequestMessageImpl.class) +@RequiredArgsConstructor +public class ArtifactRequestHandler implements MessageHandler { + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * Service for handling artifact response messages. + */ + private final @NonNull ArtifactResponseService messageService; + + /** + * Service for resolving entities. + */ + private final @NonNull EntityResolver entityResolver; + + /** + * Service for connector usage control configurations. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Service for contract processing. + */ + private final @NonNull ContractManager contractManager; + + /** + * The verifier for the data access. + */ + private final @NonNull DataProvisionVerifier accessVerifier; + + /** + * This message implements the logic that is needed to handle the message. As it returns the + * input as string the messagePayload-InputStream is converted to a String. + * + * @param message The request message. + * @param payload The message payload. + * @return The response message. + * @throws RuntimeException If the response body failed to be build. + */ + @Override + public MessageResponse handleMessage(final ArtifactRequestMessageImpl message, + final MessagePayload payload) throws RuntimeException { + // Validate incoming message. + try { + messageService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var requestedArtifact = MessageUtils.extractRequestedArtifact(message); + final var transferContract = MessageUtils.extractTransferContract(message); + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + if (requestedArtifact == null || requestedArtifact.toString().equals("")) { + // Without a requested artifact, the message processing will be aborted. + return responseService.handleMissingRequestedArtifact(requestedArtifact, + transferContract, issuer, messageId); + } + + // Check agreement only if contract negotiation is turned on. + final var negotiation = connectorConfig.isPolicyNegotiation(); + if (negotiation) { + if (transferContract == null || transferContract.toString().equals("")) { + // Without a transfer contract, the message processing will be aborted. + return responseService.handleMissingTransferContract(requestedArtifact, + transferContract, issuer, messageId); + } + + try { + final var agreement = contractManager.validateTransferContract( + transferContract, requestedArtifact); + + final var input = new VerificationInput(requestedArtifact, issuer, agreement); + if (accessVerifier.verify(input) == VerificationResult.DENIED) { + throw new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + } + } catch (ResourceNotFoundException | IllegalArgumentException exception) { + // Agreement could not be loaded or deserialized. + return responseService.handleMessageProcessingFailed(exception, + requestedArtifact, transferContract, issuer, messageId); + } catch (PolicyRestrictionException exception) { + // Conditions not fulfilled. + return responseService.handlePolicyRestrictionException(exception, + requestedArtifact, transferContract, issuer, messageId); + } catch (ContractException exception) { + // Invalid transfer contract. + return responseService.handleInvalidTransferContract(exception, requestedArtifact, + transferContract, issuer, messageId); + } + } + + // Either without contract negotiation or if all conditions are fulfilled, data is returned. + try { + // Process query input. + final var queryInput = getQueryInputFromPayload(payload); + return returnData(requestedArtifact, transferContract, issuer, messageId, queryInput); + } catch (InvalidInputException exception) { + return responseService.handleInvalidQueryInput(exception, requestedArtifact, + transferContract, issuer, messageId); + } catch (Exception exception) { + // Failed to retrieve data. + return responseService.handleFailedToRetrieveData(exception, requestedArtifact, + issuer, messageId); + } + } + + /** + * Get data by requested artifact and return within an artifact response message. + * + * @param requestedArtifact The requested artifact. + * @param transferContract The id of the transfer contract. + * @param issuer The issuer connector. + * @param messageId The message id. + * @param queryInput The query input. + * @return A message response. + */ + private MessageResponse returnData(final URI requestedArtifact, final URI transferContract, + final URI issuer, final URI messageId, + final QueryInput queryInput) { + try { + final var data = entityResolver.getDataByArtifactId(requestedArtifact, queryInput); + + // Build ids response message. + final var desc = new ArtifactResponseMessageDesc(issuer, messageId, transferContract); + final var header = messageService.buildMessage(desc); + + // Send ids response message. + return BodyResponse.create(header, Base64Utils.encodeToString(data.readAllBytes())); + } catch (MessageBuilderException | ConstraintViolationException | IOException exception) { + return responseService.handleResponseMessageBuilderException(exception, issuer, + messageId); + } + } + + /** + * Read query parameters from message payload. + * + * @param messagePayload The message's payload. + * @return the query input. + * @throws InvalidInputException If the query input is not empty but invalid. + */ + private QueryInput getQueryInputFromPayload(final MessagePayload messagePayload) + throws InvalidInputException { + try { + final var payload = MessageUtils.getStreamAsString(messagePayload); + if (payload.equals("")) { + // Query input is optional, so no rejection message will be sent. Query input will + // be checked for null value in HttpService.class. + return null; + } else { + return new ObjectMapper().readValue(payload, QueryInput.class); + } + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("Invalid query input. [exception=({})]", e.getMessage(), e); + } + throw new InvalidInputException("Invalid query input.", e); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandler.java new file mode 100644 index 000000000..87776b4af --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandler.java @@ -0,0 +1,181 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.ContractAgreementMessageImpl; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.MessageRequestException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.services.EntityUpdateService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.MessageProcessedNotificationService; +import io.dataspaceconnector.services.usagecontrol.PolicyExecutionService; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * This @{@link ContractAgreementHandler} handles all incoming messages that have a + * {@link ContractAgreementMessageImpl} as part one in the multipart message. + * This header must have the correct '@type' reference as defined in the + * {@link ContractAgreementMessageImpl} JsonTypeName annotation. + */ +@Component +@Log4j2 +@RequiredArgsConstructor +@SupportedMessageType(ContractAgreementMessageImpl.class) +public class ContractAgreementHandler implements MessageHandler { + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * Service for handling notification messages. + */ + private final @NonNull MessageProcessedNotificationService messageService; + + /** + * Service for resolving entities. + */ + private final @NonNull EntityResolver entityResolver; + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Service for updating database entities from ids object. + */ + private final @NonNull EntityUpdateService updateService; + + /** + * Policy execution point. + */ + private final @NonNull PolicyExecutionService executionService; + + /** + * This message implements the logic that is needed to handle the message. As it just returns + * the input as string the messagePayload-InputStream is converted to a String. + * + * @param message The received contract agreement message. + * @param payload The message's content. + * @return The response message. + * @throws RuntimeException if the response body failed to be build. + */ + @Override + public MessageResponse handleMessage(final ContractAgreementMessageImpl message, + final MessagePayload payload) throws RuntimeException { + // Validate incoming message. + try { + messageService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + // Read message payload as string. + String payloadAsString; + try { + payloadAsString = MessageUtils.getPayloadAsString(payload); + } catch (MessageRequestException exception) { + return responseService.handleMessagePayloadException(exception, messageId, issuer); + } + + try { + // Deserialize string to contract object. + final var agreement = deserializationService.getContractAgreement(payloadAsString); + final var agreementId = agreement.getId(); + + // Get stored ids contract agreement. + final var storedAgreement = entityResolver.getAgreementByUri(agreementId); + final var storedIdsAgreement + = deserializationService.getContractAgreement(storedAgreement.getValue()); + + // Compare both contract agreements. + if (!ContractUtils.compareContractAgreements(agreement, storedIdsAgreement)) { + return responseService.handleContractException( + new ContractException("Not the same contract."), payloadAsString, + issuer, messageId); + } + + // Update contract agreement to confirmed. + if (!updateService.confirmAgreement(storedAgreement)) { + return responseService.handleUnconfirmedAgreement(storedAgreement, issuer, + messageId); + } + + // Send contract to clearing house. + executionService.sendAgreement(agreement); + + return respondToMessage(issuer, messageId); + } catch (IllegalArgumentException exception) { + return responseService.handleIllegalArgumentException(exception, payloadAsString, + issuer, messageId); + } catch (ResourceNotFoundException exception) { + return responseService.handleMessageProcessingFailed(exception, payloadAsString, + issuer, messageId); + } catch (ContractException exception) { + return responseService.handleContractException(exception, payloadAsString, + issuer, messageId); + } + } + + /** + * Build and send response message. + * + * @param issuer The issuer connector. + * @param messageId The message id. + * @return A message response. + */ + private MessageResponse respondToMessage(final URI issuer, final URI messageId) { + try { + // Build ids response message. + final var desc = new MessageProcessedNotificationMessageDesc(issuer, messageId); + final var header = messageService.buildMessage(desc); + + // Send ids response message. + return BodyResponse.create(header, "Received contract agreement message."); + } catch (MessageBuilderException | ConstraintViolationException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandler.java new file mode 100644 index 000000000..eec7ec2ec --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandler.java @@ -0,0 +1,274 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.ContractRequestMessageImpl; +import de.fraunhofer.iais.eis.RejectionMessage; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.MessageRequestException; +import io.dataspaceconnector.exceptions.RdfBuilderException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.messages.ContractAgreementMessageDesc; +import io.dataspaceconnector.model.messages.ContractRejectionMessageDesc; +import io.dataspaceconnector.services.EntityPersistenceService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.ContractAgreementService; +import io.dataspaceconnector.services.messages.types.ContractRejectionService; +import io.dataspaceconnector.services.resources.EntityDependencyResolver; +import io.dataspaceconnector.services.usagecontrol.RuleValidator; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import javax.persistence.PersistenceException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * This @{@link ContractRequestHandler} handles all incoming messages that have a + * {@link de.fraunhofer.iais.eis.ContractRequestMessageImpl} as part one in the multipart message. + * This header must have the correct '@type' reference as defined in the + * {@link de.fraunhofer.iais.eis.ContractRequestMessageImpl} JsonTypeName annotation. + */ +@Component +@Log4j2 +@SupportedMessageType(ContractRequestMessageImpl.class) +@RequiredArgsConstructor +public class ContractRequestHandler implements MessageHandler { + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * Service for ids contract rejection messages. + */ + private final @NonNull ContractRejectionService rejectionService; + + /** + * Service for ids contract agreement messages. + */ + private final @NonNull ContractAgreementService agreementService; + + /** + * Service for resolving elements and its parents/children. + */ + private final @NonNull EntityDependencyResolver dependencyResolver; + + /** + * Service for persisting entities. + */ + private final @NonNull EntityPersistenceService persistenceService; + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Service for validating rule content. + */ + private final @NonNull RuleValidator ruleValidator; + + /** + * This message implements the logic that is needed to handle the message. As it just returns + * the input as string the messagePayload-InputStream is converted to a String. + * + * @param message The ids request message as header. + * @param payload The request message payload. + * @return The response message. + */ + @Override + public MessageResponse handleMessage(final ContractRequestMessageImpl message, + final MessagePayload payload) { + // Validate incoming message. + try { + agreementService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + // Read message payload as string. + String payloadAsString; + try { + payloadAsString = MessageUtils.getPayloadAsString(payload); + } catch (MessageRequestException exception) { + return responseService.handleMessagePayloadException(exception, messageId, issuer); + } + + // Check the contract's content. + return processContractRequest(payloadAsString, messageId, issuer); + } + + /** + * Checks if the contract request content by the consumer complies with the contract offer by + * the provider. + * + * @param payload The message payload containing a contract request. + * @param messageId The message id of the incoming message. + * @param issuer The issuer connector extracted from the incoming message. + * @return A message response to the requesting connector. + */ + public MessageResponse processContractRequest(final String payload, final URI messageId, + final URI issuer) throws RuntimeException { + try { + // Deserialize string to contract object. + final var request = deserializationService.getContractRequest(payload); + + // Get all rules of the contract request. + final var rules = ContractUtils.extractRulesFromContract(request); + if (rules.isEmpty()) { + // Return rejection message if the contract request is missing rules. + return responseService.handleMissingRules(request, messageId, issuer); + } + + final var targetRuleMap = ContractUtils.getTargetRuleMap(rules); + if (targetRuleMap.containsKey(null)) { + // Return rejection message if the rules are missing targets. + return responseService.handleMissingTargetInRules(request, messageId, issuer); + } + + final var targetList = new ArrayList(); + // Retrieve matching contract offers to compare the content. + for (final var target : targetRuleMap.keySet()) { + final List contracts; + try { + contracts = dependencyResolver.getContractOffersByArtifactId(target); + } catch (ResourceNotFoundException exception) { + return responseService.handleResourceNotFoundException(exception, target, + issuer, messageId); + } + + // Abort negotiation if no contract offer could be found. + if (contracts.isEmpty()) { + return responseService.handleMissingContractOffers(request, messageId, issuer); + } + + // Abort negotiation if no contract offer for the issuer connector could be found. + final var validContracts + = ContractUtils.removeContractsWithInvalidConsumer(contracts, issuer); + if (validContracts.isEmpty()) { + return responseService.handleMissingContractOffers(request, messageId, issuer); + } + + var valid = false; + try { + valid = ruleValidator.validateRulesOfRequest(validContracts, targetRuleMap, + target); + } catch (IllegalArgumentException e) { + return responseService.handleMalformedRules(e, payload, issuer, messageId); + } + + if (!valid) { + return rejectContract(issuer, messageId); + } + targetList.add(target); + } + + return acceptContract(request, issuer, messageId, targetList); + } catch (IllegalArgumentException e) { + return responseService.handleIllegalArgumentException(e, payload, issuer, messageId); + } catch (Exception e) { + // NOTE: Should not be reached. + return responseService.handleMessageProcessingFailed(e, payload, issuer, messageId); + } + } + + /** + * Accept contract by building a contract agreement and sending it as payload within a + * contract agreement message. + * + * @param request The contract request object from the data consumer. + * @param issuer The issuer connector id. + * @param messageId The correlation message id. + * @param targets List of requested targets. + * @return The message response to the requesting connector. + */ + private MessageResponse acceptContract(final ContractRequest request, final URI issuer, + final URI messageId, final List targets) { + ContractAgreement agreement = null; + URI agreementId; + try { + // Turn the accepted contract request into a contract agreement and persist it. + agreement = persistenceService.buildAndSaveContractAgreement(request, targets, issuer); + agreementId = agreement.getId(); + } catch (ConstraintViolationException | PersistenceException exception) { + return responseService.handleAgreementPersistenceException(exception, agreement, + issuer, messageId); + } + + try { + // Build ids response message. + final var desc = new ContractAgreementMessageDesc(issuer, messageId); + final var header = agreementService.buildMessage(desc); + if (log.isDebugEnabled()) { + log.debug("Contract request accepted. [agreementId=({})]", agreementId); + } + + // Send ids response message. + return BodyResponse.create(header, agreement.toRdf()); + } catch (MessageBuilderException | IllegalStateException | ConstraintViolationException + | RdfBuilderException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } + + /** + * Builds a contract rejection message with a rejection reason. + * + * @param issuer The issuer connector. + * @param messageId The correlation message id. + * @return A contract rejection message. + */ + private MessageResponse rejectContract(final URI issuer, final URI messageId) { + try { + // Build ids response message. + final var desc = new ContractRejectionMessageDesc(issuer, messageId); + final var header = (RejectionMessage) rejectionService.buildMessage(desc); + + // Send ids response message. + return ErrorResponse.create(header, "Contract rejected."); + } catch (MessageBuilderException | IllegalStateException | ConstraintViolationException + | RdfBuilderException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandler.java new file mode 100644 index 000000000..e9b4165a7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandler.java @@ -0,0 +1,166 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.DescriptionRequestMessageImpl; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.InvalidResourceException; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.SelfLinkCreationException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.messages.DescriptionResponseMessageDesc; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.DescriptionResponseService; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * This @{@link DescriptionRequestHandler} handles all incoming messages that have a + * {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} as part one in the multipart + * message. This header must have the correct '@type' reference as defined in the + * {@link de.fraunhofer.iais.eis.DescriptionRequestMessageImpl} JsonTypeName annotation. + */ +@Component +@RequiredArgsConstructor +@SupportedMessageType(DescriptionRequestMessageImpl.class) +public class DescriptionRequestHandler implements MessageHandler { + + /** + * Service for handling response messages. + */ + private final @NonNull DescriptionResponseService messageService; + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * Service for the current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Service for resolving entities. + */ + private final @NonNull EntityResolver entityResolver; + + /** + * This message implements the logic that is needed to handle the message. As it just returns + * the input as string the messagePayload-InputStream is converted to a String. + * + * @param message The ids request message as header. + * @param payload The request message payload. + * @return The response message. + */ + @Override + public MessageResponse handleMessage(final DescriptionRequestMessageImpl message, + final MessagePayload payload) { + // Validate incoming message. + try { + messageService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var requested = MessageUtils.extractRequestedElement(message); + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + // Check if a specific resource has been requested. + if (requested == null) { + return constructSelfDescription(issuer, messageId); + } else { + return constructResourceDescription(requested, issuer, messageId); + } + } + + /** + * Constructs the response message for a given resource description request message. + * + * @param requested The requested element. + * @param issuer The issuer connector extracted from the incoming message. + * @param messageId The message id of the incoming message. + * @return The response message to the passed request. + */ + public MessageResponse constructResourceDescription(final URI requested, + final URI issuer, + final URI messageId) { + try { + final var entity = entityResolver.getEntityById(requested); + + if (entity == null) { + throw new ResourceNotFoundException(ErrorMessages.EMTPY_ENTITY.toString()); + } else { + // If the element has been found, build the ids response message. + final var desc = new DescriptionResponseMessageDesc(issuer, messageId); + final var header = messageService.buildMessage(desc); + final var payload = entityResolver.getEntityAsRdfString(entity); + + // Send ids response message. + return BodyResponse.create(header, payload); + } + } catch (ResourceNotFoundException | InvalidResourceException e) { + return responseService.handleResourceNotFoundException(e, requested, issuer, messageId); + } catch (MessageBuilderException | IllegalStateException | ConstraintViolationException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } catch (SelfLinkCreationException exception) { + return responseService.handleSelfLinkCreationException(exception, requested); + } + } + + /** + * Constructs a resource catalog description message for the connector. + * + * @param issuer The issuer connector extracted from the incoming message. + * @param messageId The message id of the incoming message. + * @return A response message containing the resource catalog of the connector. + */ + public MessageResponse constructSelfDescription(final URI issuer, final URI messageId) { + try { + // Get self-description. + // TODO Only return contract offers that have no or the right pre-defined consumer + final var connector = connectorService.getConnectorWithOfferedResources(); + + // Build ids response message. + final var desc = new DescriptionResponseMessageDesc(issuer, messageId); + final var header = messageService.buildMessage(desc); + + // Send ids response message. + return BodyResponse.create(header, connector.toRdf()); + } catch (MessageBuilderException | IllegalStateException | ConstraintViolationException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java new file mode 100644 index 000000000..c9d0aec38 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.NotificationMessageImpl; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.MessageProcessedNotificationService; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * This @{@link NotificationMessageHandler} handles all incoming messages that have a + * {@link de.fraunhofer.iais.eis.NotificationMessageImpl} as part one in the multipart message. + * This header must have the correct '@type' reference as defined in the + * {@link de.fraunhofer.iais.eis.NotificationMessageImpl} JsonTypeName annotation. + */ +@Component +@SupportedMessageType(NotificationMessageImpl.class) +@RequiredArgsConstructor +public class NotificationMessageHandler implements MessageHandler { + + /** + * Service for handling message processed notification messages. + */ + private final @NonNull MessageProcessedNotificationService messageService; + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * This message implements the logic that is needed to handle the message. As it just returns + * the input as string the messagePayload-InputStream is converted to a String. + * + * @param message The ids notification message as header. + * @param payload The message notification message's content. + * @return The response message. + */ + @Override + public MessageResponse handleMessage(final NotificationMessageImpl message, + final MessagePayload payload) { + // Validate incoming message. + try { + messageService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + try { + // Build the ids response. + final var desc = new MessageProcessedNotificationMessageDesc(issuer, messageId); + final var header = messageService.buildMessage(desc); + return BodyResponse.create(header, "Message received."); + } catch (IllegalStateException | ConstraintViolationException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandler.java b/src/main/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandler.java new file mode 100644 index 000000000..4aee491a9 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandler.java @@ -0,0 +1,169 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.ResourceUpdateMessageImpl; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.services.EntityUpdateService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.messages.MessageResponseService; +import io.dataspaceconnector.services.messages.types.MessageProcessedNotificationService; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessageHandler; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.SupportedMessageType; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.MessageResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; + +/** + * This @{@link ResourceUpdateMessageHandler} handles all incoming messages that have a + * {@link de.fraunhofer.iais.eis.ResourceUpdateMessageImpl} as part one in the multipart message. + * This header must have the correct '@type' reference as defined in the + * {@link ResourceUpdateMessageImpl} JsonTypeName annotation. + */ + +@Component +@Log4j2 +@RequiredArgsConstructor +@SupportedMessageType(ResourceUpdateMessageImpl.class) +public class ResourceUpdateMessageHandler implements MessageHandler { + + /** + * Service for building and sending message responses. + */ + private final @NonNull MessageResponseService responseService; + + /** + * Service for handling response messages. + */ + private final @NonNull MessageProcessedNotificationService messageService; + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Service for updating database entities from ids object. + */ + private final @NonNull EntityUpdateService updateService; + + /** + * This message implements the logic that is needed to handle the message. As it just returns + * the input as string the messagePayload-InputStream is converted to a String. + * + * @param message The ids request message as header. + * @param payload The notification message payload. + * @return The response message. + */ + @Override + public MessageResponse handleMessage(final ResourceUpdateMessageImpl message, + final MessagePayload payload) throws RuntimeException { + // Validate incoming message. + try { + messageService.validateIncomingMessage(message); + } catch (MessageEmptyException exception) { + return responseService.handleMessageEmptyException(exception); + } catch (VersionNotSupportedException exception) { + return responseService.handleInfoModelNotSupportedException(exception, + message.getModelVersion()); + } + + // Read relevant parameters for message processing. + final var affected = MessageUtils.extractAffectedResource(message); + final var issuer = MessageUtils.extractIssuerConnector(message); + final var messageId = MessageUtils.extractMessageId(message); + + if (affected == null || affected.toString().isEmpty()) { + // Without an affected resource, the message processing will be aborted. + return responseService.handleMissingAffectedResource(affected, issuer, messageId); + } + + String payloadAsString; + try { + // Try to read payload as string. + payloadAsString = MessageUtils.getStreamAsString(payload); + if (payloadAsString.isEmpty()) { + return responseService.handleMissingPayload(affected, issuer, messageId); + } + } catch (IOException | IllegalArgumentException e) { + return responseService.handleMessagePayloadException(e, messageId, issuer); + } + + return updateResource(payloadAsString, affected, issuer, messageId); + } + + /** + * Update resource in internal database. + * + * @param payload The payload as string. + * @param affected The affected resource. + * @param issuer The issuer connector. + * @param messageId The message id. + * @return A message response. + */ + private MessageResponse updateResource(final String payload, final URI affected, + final URI issuer, final URI messageId) { + // Get ids resource from payload. + try { + final var resource = deserializationService.getResource(payload); + final var resourceId = resource.getId(); + + // Check if the resource id and affected resource id match. + if (!resourceId.equals(affected)) { + return responseService.handleInvalidAffectedResource(resourceId, affected, issuer, + messageId); + } + + // Update requested resource with received information. + updateService.updateResource(resource); + } catch (IllegalArgumentException e) { + return responseService.handleIllegalArgumentException(e, payload, issuer, messageId); + } + + // Respond although updating the resource may have failed. + return respondToMessage(issuer, messageId); + } + + /** + * Build and send response message. + * + * @param issuer The issuer connector. + * @param messageId The message id. + * @return A message response. + */ + private MessageResponse respondToMessage(final URI issuer, final URI messageId) { + try { + // Build ids response message. + final var desc = new MessageProcessedNotificationMessageDesc(issuer, messageId); + final var header = messageService.buildMessage(desc); + + // Send ids response message. + return BodyResponse.create(header, "Message received."); + } catch (IllegalStateException e) { + return responseService.handleResponseMessageBuilderException(e, issuer, messageId); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/handler/package-info.java b/src/main/java/io/dataspaceconnector/services/messages/handler/package-info.java new file mode 100644 index 000000000..fe42d3a9b --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/handler/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Handler for receiving incoming IDS messages. + */ +package io.dataspaceconnector.services.messages.handler; diff --git a/src/main/java/io/dataspaceconnector/services/messages/package-info.java b/src/main/java/io/dataspaceconnector/services/messages/package-info.java new file mode 100644 index 000000000..dcd300bde --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Services for handling IDS messages. + */ +package io.dataspaceconnector.services.messages; diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/AbstractMessageService.java b/src/main/java/io/dataspaceconnector/services/messages/types/AbstractMessageService.java new file mode 100644 index 000000000..ea07c1853 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/AbstractMessageService.java @@ -0,0 +1,231 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.RejectionMessage; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.model.messages.MessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.MessageUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.daps.ClaimsException; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.fileupload.FileUploadException; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract class for building, sending, and processing ids messages. + * + * @param The type of the message description. + */ +@Log4j2 +public abstract class AbstractMessageService { + + /** + * Service for ids communication. + */ + @Autowired + private IDSHttpService idsHttpService; + + /** + * Service for the current connector configuration. + */ + @Autowired + private ConnectorService connectorService; + + /** + * Service for ids deserialization. + */ + @Autowired + private DeserializationService deserializationService; + + /** + * Build ids message with params. + * + * @param desc Type-specific message parameter. + * @return An ids message. + * @throws ConstraintViolationException If the ids message could not be built. + */ + public abstract Message buildMessage(D desc) throws ConstraintViolationException; + + /** + * Return allowed response message type. + * + * @return The response message type class. + */ + protected abstract Class getResponseMessageType(); + + /** + * Build and sent a multipart message with header and payload. + * + * @param desc Type-specific message parameter. + * @param payload The message's payload. + * @return The response as map. + * @throws MessageException If message building, sending, or processing failed. + */ + public Map send(final D desc, final Object payload) + throws MessageException { + try { + final var recipient = desc.getRecipient(); + final var header = buildMessage(desc); + + final var body = MessageUtils.buildIdsMultipartMessage(header, payload); + if (log.isDebugEnabled()) { + // TODO Add logging house class + log.debug("Built request message. [body=({})]", body); + } + + // Send message and return response. + return idsHttpService.sendAndCheckDat(body, recipient); + } catch (MessageBuilderException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to build ids request message. [exception=({})]", + e.getMessage(), e); + } + throw new MessageException(ErrorMessages.MESSAGE_BUILD_FAILED.toString(), e); + } catch (MessageResponseException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to read ids response message. [exception=({})]", + e.getMessage(), e); + } + throw new MessageException(ErrorMessages.INVALID_RESPONSE.toString(), e); + } catch (ConstraintViolationException e) { + if (log.isWarnEnabled()) { + log.warn("Ids message could not be built. [exception=({})]", + e.getMessage(), e); + } + throw new MessageException(ErrorMessages.HEADER_BUILD_FAILED.toString(), e); + } catch (ClaimsException e) { + if (log.isDebugEnabled()) { + log.debug("Invalid DAT in incoming message. [exception=({})]", + e.getMessage(), e); + } + throw new MessageException(ErrorMessages.INVALID_RESPONSE_DAT.toString(), e); + } catch (FileUploadException | IOException e) { + if (log.isWarnEnabled()) { + log.warn("Message could not be sent. [exception=({})]", e.getMessage(), e); + } + throw new MessageException(ErrorMessages.MESSAGE_NOT_SENT.toString(), e); + } + } + + /** + * Checks if the response message is of the right type. + * + * @param message The received message response. + * @return True if the response type is as expected. + * @throws MessageResponseException If the response could not be read. + */ + public boolean isValidResponseType(final Map message) + throws MessageResponseException { + try { + // MessageResponseException is handled at a higher level. + final var header = MessageUtils.extractHeaderFromMultipartMessage(message); + final var idsMessage = getDeserializer().getMessage(header); + + final var messageType = idsMessage.getClass(); + final var allowedType = getResponseMessageType(); + return messageType.equals(allowedType); + } catch (MessageResponseException | IllegalArgumentException e) { + if (log.isDebugEnabled()) { + log.debug("Failed to read response header. [exception=({})]", e.getMessage(), e); + } + throw new MessageResponseException(ErrorMessages.MALFORMED_HEADER.toString(), e); + } catch (Exception e) { + // NOTE: Should not be reached. + if (log.isWarnEnabled()) { + log.warn("Something else went wrong. [exception=({})]", e.getMessage()); + } + throw new MessageResponseException(ErrorMessages.INVALID_RESPONSE.toString(), e); + } + } + + /** + * The ids message. + * + * @param message The message that should be validated. + * @throws MessageEmptyException if the message is empty. + * @throws VersionNotSupportedException if the message version is not supported. + */ + public void validateIncomingMessage(final Message message) throws MessageEmptyException, + VersionNotSupportedException { + MessageUtils.checkForEmptyMessage(message); + + final var modelVersion = MessageUtils.extractModelVersion(message); + final var inboundVersions = connectorService.getInboundModelVersion(); + MessageUtils.checkForVersionSupport(modelVersion, inboundVersions); + } + + /** + * If the response message is not of the expected type, message type, rejection reason, and the + * payload are returned as an object. + * + * @param message The ids multipart message as map. + * @return The object. + * @throws MessageResponseException Of the response could not be read or deserialized. + * @throws IllegalArgumentException If deserialization fails. + */ + public Map getResponseContent(final Map message) + throws MessageResponseException, IllegalArgumentException { + final var header = MessageUtils.extractHeaderFromMultipartMessage(message); + final var payload = MessageUtils.extractPayloadFromMultipartMessage(message); + + final var idsMessage = deserializationService.getResponseMessage(header); + var responseMap = new HashMap() {{ + put("type", idsMessage.getClass()); + }}; + + // If the message is of type exception, add the reason to the response object. + if (idsMessage instanceof RejectionMessage) { + final var rejectionMessage = (RejectionMessage) idsMessage; + final var reason = MessageUtils.extractRejectionReason(rejectionMessage); + responseMap.put("reason", reason); + } + + responseMap.put("payload", payload); + return responseMap; + } + + /** + * Getter for ids connector service. + * + * @return The service class. + */ + public ConnectorService getConnectorService() { + return connectorService; + } + + /** + * Getter for ids deserialization service. + * + * @return The service class. + */ + public DeserializationService getDeserializer() { + return deserializationService; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactRequestService.java b/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactRequestService.java new file mode 100644 index 000000000..454e4697a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactRequestService.java @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import java.net.URI; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ArtifactRequestMessageBuilder; +import de.fraunhofer.iais.eis.ArtifactResponseMessageImpl; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.model.messages.ArtifactRequestMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids artifact request messages. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public final class ArtifactRequestService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final ArtifactRequestMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var artifactId = desc.getRequestedArtifact(); + final var contractId = desc.getTransferContract(); + + return new ArtifactRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._requestedArtifact_(artifactId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return ArtifactResponseMessageImpl.class; + } + + /** + * Build and send an artifact request message. + * + * @param recipient The recipient. + * @param elementId The requested artifact. + * @param agreementId The transfer contract. + * @return The response map. + * @throws MessageException If message handling failed. + */ + public Map sendMessage(final URI recipient, final URI elementId, + final URI agreementId) throws MessageException { + return sendMessage(recipient, elementId, agreementId, null); + } + + /** + * Send artifact request message. + * + * @param recipient The recipient. + * @param elementId The requested artifact. + * @param agreementId The transfer contract. + * @param queryInput The query input. + * @return The response map. + * @throws MessageException If message handling failed. + */ + public Map sendMessage( + final URI recipient, final URI elementId, final URI agreementId, + final QueryInput queryInput) throws MessageException { + String payload = ""; + if (queryInput != null) { + try { + payload = new ObjectMapper().writeValueAsString(queryInput); + } catch (JsonProcessingException e) { + if (log.isDebugEnabled()) { + log.debug("Failed to parse query. Loading everything. [exception=({})]", + e.getMessage(), e); + } + } + } + + return send(new ArtifactRequestMessageDesc(recipient, elementId, agreementId), payload); + } + + /** + * Check if the response message is of type artifact response. + * + * @param response The response as map. + * @return True if the response type is as expected. + * @throws MessageResponseException if the response could not be read. + */ + public boolean validateResponse(final Map response) + throws MessageResponseException { + return isValidResponseType(response); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactResponseService.java b/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactResponseService.java new file mode 100644 index 000000000..bee183a71 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/ArtifactResponseService.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ArtifactResponseMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.messages.ArtifactResponseMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids artifact response messages. + */ +@Service +public final class ArtifactResponseService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final ArtifactResponseMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var contractId = desc.getTransferContract(); + final var correlationMessage = desc.getCorrelationMessage(); + + return new ArtifactResponseMessageBuilder() + ._securityToken_(token) + ._correlationMessage_(correlationMessage) + ._issued_(getGregorianNow()) + ._issuerConnector_(connectorId) + ._modelVersion_(modelVersion) + ._senderAgent_(connectorId) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/ContractAgreementService.java b/src/main/java/io/dataspaceconnector/services/messages/types/ContractAgreementService.java new file mode 100644 index 000000000..e38538d33 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/ContractAgreementService.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import java.net.URI; +import java.util.Map; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractAgreementMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessageImpl; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.model.messages.ContractAgreementMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.IdsUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids contract agreement messages. + */ +@Service +public final class ContractAgreementService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException if desc is null. + */ + @Override + public Message buildMessage(final ContractAgreementMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var correlationMessage = desc.getCorrelationMessage(); + + return new ContractAgreementMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + ._correlationMessage_(correlationMessage) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return MessageProcessedNotificationMessageImpl.class; + } + + /** + * Build and send a contract agreement message. + * + * @param recipient The recipient. + * @param agreement The contract agreement. + * @return The response map. + * @throws MessageException if message handling failed. + * @throws io.dataspaceconnector.exceptions.RdfBuilderException + * if the contract agreement rdf string could not be built. + * @throws IllegalArgumentException if contract agreement is null. + */ + public Map sendMessage(final URI recipient, final ContractAgreement agreement) + throws MessageException, ConstraintViolationException { + Utils.requireNonNull(agreement, ErrorMessages.ENTITY_NULL); + + final var contractRdf = IdsUtils.toRdf(agreement); + return send(new ContractAgreementMessageDesc(recipient, agreement.getId()), contractRdf); + } + + /** + * Check if the response message is of type message processed notification. + * + * @param response The response as map. + * @return True if the response type is as expected. + * @throws MessageResponseException if the response could not be read. + */ + public boolean validateResponse(final Map response) + throws MessageResponseException { + return isValidResponseType(response); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/ContractRejectionService.java b/src/main/java/io/dataspaceconnector/services/messages/types/ContractRejectionService.java new file mode 100644 index 000000000..fa3950bfa --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/ContractRejectionService.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ContractRejectionMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.messages.ContractRejectionMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids contract request messages. + */ +@Service +public final class ContractRejectionService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final ContractRejectionMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var correlationMessage = desc.getCorrelationMessage(); + final var rejectionReason = new TypedLiteral("Contract not accepted.", "en"); + + return new ContractRejectionMessageBuilder() + ._securityToken_(token) + ._correlationMessage_(correlationMessage) + ._issued_(getGregorianNow()) + ._issuerConnector_(connectorId) + ._modelVersion_(modelVersion) + ._senderAgent_(connectorId) + ._recipientConnector_(Util.asList(recipient)) + ._rejectionReason_(RejectionReason.MALFORMED_MESSAGE) + ._contractRejectionReason_(rejectionReason) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/ContractRequestService.java b/src/main/java/io/dataspaceconnector/services/messages/types/ContractRequestService.java new file mode 100644 index 000000000..6d4124d35 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/ContractRequestService.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import java.net.URI; +import java.util.Map; + +import de.fraunhofer.iais.eis.ContractAgreementMessageImpl; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.ContractRequestMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.exceptions.RdfBuilderException; +import io.dataspaceconnector.model.messages.ContractRequestMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.IdsUtils; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids contract request messages. + */ +@Service +public final class ContractRequestService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final ContractRequestMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var contractId = desc.getTransferContract(); + + return new ContractRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return ContractAgreementMessageImpl.class; + } + + /** + * Build and send a description request message. + * + * @param recipient The recipient. + * @param request The contract request. + * @return The response map. + * @throws MessageException If message handling failed. + * @throws RdfBuilderException If the contract request rdf string could not be built. + * @throws IllegalArgumentException If contract request is null. + */ + public Map sendMessage(final URI recipient, final ContractRequest request) + throws MessageException, RdfBuilderException { + Utils.requireNonNull(request, ErrorMessages.ENTITY_NULL); + + final var contractRdf = IdsUtils.toRdf(request); + return send(new ContractRequestMessageDesc(recipient, request.getId()), contractRdf); + } + + /** + * Check if the response message is of type contract agreement. + * + * @param response The response as map. + * @return True if the response type is as expected. + * @throws MessageResponseException If the response could not be read. + */ + public boolean validateResponse(final Map response) + throws MessageResponseException { + return isValidResponseType(response); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionRequestService.java b/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionRequestService.java new file mode 100644 index 000000000..eec1d9b8e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionRequestService.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import java.net.URI; +import java.util.Map; + +import de.fraunhofer.iais.eis.DescriptionRequestMessageBuilder; +import de.fraunhofer.iais.eis.DescriptionResponseMessageImpl; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.model.messages.DescriptionRequestMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids description request messages. + */ +@Service +public final class DescriptionRequestService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final DescriptionRequestMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var elementId = desc.getRequestedElement(); + + return new DescriptionRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._requestedElement_(elementId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return DescriptionResponseMessageImpl.class; + } + + /** + * Build and send a description request message. + * + * @param recipient The recipient. + * @param elementId The requested element. + * @return The response map. + * @throws MessageException If message handling failed. + */ + public Map sendMessage(final URI recipient, final URI elementId) + throws MessageException { + return send(new DescriptionRequestMessageDesc(recipient, elementId), ""); + } + + /** + * Check if the response message is of type description response. + * + * @param response The response as map. + * @return True if the response type is as expected. + * @throws MessageResponseException If the response could not be read. + */ + public boolean validateResponse(final Map response) + throws MessageResponseException { + return isValidResponseType(response); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionResponseService.java b/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionResponseService.java new file mode 100644 index 000000000..bbab9d1c7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/DescriptionResponseService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DescriptionResponseMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.messages.DescriptionResponseMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids description response messages. + */ +@Service +public final class DescriptionResponseService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final DescriptionResponseMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var correlationMessage = desc.getCorrelationMessage(); + + return new DescriptionResponseMessageBuilder() + ._securityToken_(token) + ._correlationMessage_(correlationMessage) + ._issued_(getGregorianNow()) + ._issuerConnector_(connectorId) + ._modelVersion_(modelVersion) + ._senderAgent_(connectorId) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/LogMessageService.java b/src/main/java/io/dataspaceconnector/services/messages/types/LogMessageService.java new file mode 100644 index 000000000..9a46fa1e5 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/LogMessageService.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import java.net.URI; + +import de.fraunhofer.iais.eis.LogMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.PolicyExecutionException; +import io.dataspaceconnector.model.messages.LogMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids log messages. + */ +@Log4j2 +@Service +public final class LogMessageService extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final LogMessageDesc desc) throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + + return new LogMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } + + /** + * Send a message to the clearing house. Allow the access only if that operation was successful. + * + * @param recipient The message's recipient. + * @param logItem The item that should be logged. + * @throws PolicyExecutionException if the access could not be successfully logged. + */ + public void sendMessage(final URI recipient, final Object logItem) + throws PolicyExecutionException { + try { + final var response = send(new LogMessageDesc(recipient), logItem); + if (response == null) { + if (log.isDebugEnabled()) { + log.debug("No response received."); + } + throw new PolicyExecutionException("Log message has no valid response."); + } + } catch (MessageException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to send log message. [exception=({})]", e.getMessage(), e); + } + throw new PolicyExecutionException("Log message could not be sent."); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationService.java b/src/main/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationService.java new file mode 100644 index 000000000..42eb96281 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationService.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessageBuilder; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.stereotype.Service; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids message processed notification messages. + */ +@Service +public final class MessageProcessedNotificationService + extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final MessageProcessedNotificationMessageDesc desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + final var correlationMessage = desc.getCorrelationMessage(); + + return new MessageProcessedNotificationMessageBuilder() + ._securityToken_(token) + ._correlationMessage_(correlationMessage) + ._issued_(getGregorianNow()) + ._issuerConnector_(connectorId) + ._modelVersion_(modelVersion) + ._senderAgent_(connectorId) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/NotificationService.java b/src/main/java/io/dataspaceconnector/services/messages/types/NotificationService.java new file mode 100644 index 000000000..f6d53d04c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/NotificationService.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.NotificationMessageBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageException; +import io.dataspaceconnector.exceptions.PolicyExecutionException; +import io.dataspaceconnector.model.messages.NotificationMessageDesc; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.net.URI; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * Message service for ids notification messages. + */ +@Log4j2 +@Service +public final class NotificationService extends AbstractMessageService { + + /** + * @throws IllegalArgumentException If desc is null. + */ + @Override + public Message buildMessage(final NotificationMessageDesc desc) + throws ConstraintViolationException { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + final var connectorId = getConnectorService().getConnectorId(); + final var modelVersion = getConnectorService().getOutboundModelVersion(); + final var token = getConnectorService().getCurrentDat(); + + final var recipient = desc.getRecipient(); + + return new NotificationMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + @Override + protected Class getResponseMessageType() { + return null; + } + + /** + * Send a notification message. Allow the access only if that operation was successful. + * + * @param recipient The message's recipient. + * @param logItem The item that should be logged. + * @throws PolicyExecutionException if sending the notification message was unsuccessful. + */ + public void sendMessage(final URI recipient, final Object logItem) + throws PolicyExecutionException { + try { + final var response = send(new NotificationMessageDesc(recipient), logItem); + if (response == null) { + if (log.isDebugEnabled()) { + log.debug("No response received."); + } + throw new PolicyExecutionException("Notification has no valid response."); + } + } catch (MessageException e) { + if (log.isDebugEnabled()) { + log.debug("Notification not sent. [exception=({})]", e.getMessage(), e); + } + throw new PolicyExecutionException("Notification was not successful."); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/messages/types/package-info.java b/src/main/java/io/dataspaceconnector/services/messages/types/package-info.java new file mode 100644 index 000000000..c65c8080d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/messages/types/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Services for the different IDS message types. + */ +package io.dataspaceconnector.services.messages.types; diff --git a/src/main/java/io/dataspaceconnector/services/package-info.java b/src/main/java/io/dataspaceconnector/services/package-info.java new file mode 100644 index 000000000..aba8e7c6c --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Contains all services of the Dataspace Connector. + */ +package io.dataspaceconnector.services; diff --git a/src/main/java/io/dataspaceconnector/services/resources/AbstractCatalogResourceLinker.java b/src/main/java/io/dataspaceconnector/services/resources/AbstractCatalogResourceLinker.java new file mode 100644 index 000000000..0f5b4b842 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/AbstractCatalogResourceLinker.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.List; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.Resource; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Base class for handling catalog-resource relations. + * @param The resource type. + */ +public abstract class AbstractCatalogResourceLinker + extends OwningRelationService> { + /** + * Default constructor. + */ + protected AbstractCatalogResourceLinker() { + super(); + } +} + +/** + * Handles the relation between a catalog and its offered resources. + */ +@Service +@NoArgsConstructor +class CatalogOfferedResourceLinker extends AbstractCatalogResourceLinker { + @Override + protected List getInternal(final Catalog owner) { + return owner.getOfferedResources(); + } +} + +/** + * Handles the relation between a catalog and its requested resources. + */ +@Service +@NoArgsConstructor +class CatalogRequestedResourceLinker extends AbstractCatalogResourceLinker { + @Override + protected List getInternal(final Catalog owner) { + return owner.getRequestedResources(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/AbstractRelationService.java b/src/main/java/io/dataspaceconnector/services/resources/AbstractRelationService.java new file mode 100644 index 000000000..062c7dce2 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/AbstractRelationService.java @@ -0,0 +1,215 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Creates a parent-children relationship between two types of resources. + * + * @param The type of the parent resource. + * @param The type of the child resource. + * @param The service type for the parent resource. + * @param The service type for the child resource. + */ +public abstract class AbstractRelationService, X extends BaseEntityService> + implements RelationService { + + /* + NOTE: Pretty much all functions will throw an ResourceNotFoundException but they are not + added to the function signature. The basic idea here was, that a request to an missing + entity can only come from an user and in most cases will be handled by an + ResourceNotFoundExceptionHandler. By handling the exception this way the calling controller + does not need to known (and thus not care for the case) how an invalid request should be + handled. + */ + + /** + * The service for the entity whose relations are modified. + **/ + @Autowired + private T oneService; + + /** + * The service for the children. + **/ + @Autowired + private X manyService; + + /** + * {@inheritDoc} + */ + @Override + public Page get(final UUID ownerId, final Pageable pageable) { + Utils.requireNonNull(ownerId, ErrorMessages.ENTITYID_NULL); + Utils.requireNonNull(pageable, ErrorMessages.PAGEABLE_NULL); + + final var owner = oneService.get(ownerId); + return getInternal(owner, pageable); + } + + /** + * Receives the list of children assigned to the entity. + * + * @param owner The entity whose children should be received. + * @return The children assigned to the entity. + */ + protected abstract List getInternal(K owner); + + /** + * Receives a page of children assigned to the entity. + * + * @param owner The entity whose children should be received. + * @param pageable The children assigned to the entity. + * @return The page of the children entities. + */ + protected Page getInternal(final K owner, final Pageable pageable) { + final var entities = getInternal(owner); + return Utils.toPage(entities, pageable); + } + + /** + * {@inheritDoc} + */ + @Override + public void add(final UUID ownerId, final Set entities) { + Utils.requireNonNull(ownerId, ErrorMessages.ENTITYID_NULL); + Utils.requireNonNull(entities, ErrorMessages.ENTITYSET_NULL); + + if (entities.isEmpty()) { + // Prevent read call to database for the owner. + return; + } + + throwIfEntityDoesNotExist(entities); + + addInternal(ownerId, entities); + } + + /** + * {@inheritDoc} + */ + @Override + public void remove(final UUID ownerId, final Set entities) { + Utils.requireNonNull(ownerId, ErrorMessages.ENTITYID_NULL); + Utils.requireNonNull(entities, ErrorMessages.ENTITYSET_NULL); + + if (entities.isEmpty()) { + // Prevent read call to database for the owner. + return; + } + + throwIfEntityDoesNotExist(entities); + + removeInternal(ownerId, entities); + } + + /** + * {@inheritDoc} + */ + @Override + public void replace(final UUID ownerId, final Set entities) { + Utils.requireNonNull(ownerId, ErrorMessages.ENTITYID_NULL); + Utils.requireNonNull(entities, ErrorMessages.ENTITYSET_NULL); + throwIfEntityDoesNotExist(entities); + + replaceInternal(ownerId, entities); + } + + /** + * Check if all entities in a set are known to the children's service. + * + * @param entities The set of entities to be checked. + * @throws ResourceNotFoundException if any of the entities is unknown. + */ + private void throwIfEntityDoesNotExist(final Set entities) { + if (!doesExist(entities, (x) -> manyService.doesExist(x))) { + throw new ResourceNotFoundException("Could not find resource."); + } + } + + /** + * Check if all entities in a set are known. + * + * @param entities The set of entities to be checked. + * @param doesElementExist The function that evaluates if an entity does exist. + * @return true if all entities are known. + */ + private boolean doesExist( + final Set entities, final Function doesElementExist) { + for (final var entity : entities) { + if (!doesElementExist.apply(entity)) { + return false; + } + } + + return true; + } + + /** + * Adds a list of children to an entity. + * + * @param ownerId ID of the owning entity. + * @param entities list of the children's IDs. + */ + protected abstract void addInternal(UUID ownerId, Set entities); + + /** + * Removes a list of children from an entity. + * + * @param ownerId ID of the owning entity. + * @param entities list of the children's IDs. + */ + protected abstract void removeInternal(UUID ownerId, Set entities); + + /** + * Replaces the list of children for an entity. + * + * @param ownerId ID of the owning entity. + * @param entities list of the children's IDs. + */ + protected abstract void replaceInternal(UUID ownerId, Set entities); + + /** + * Returns the service for managing the "one"-side of the one-to-many-relationship. + * + * @return the service. + */ + protected final T getOneService() { + return oneService; + } + + /** + * Returns the service for managing the "many"-side of the one-to-many-relationship. + * + * @return the service. + */ + protected final X getManyService() { + return manyService; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceContractLinker.java b/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceContractLinker.java new file mode 100644 index 000000000..59f434cd1 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceContractLinker.java @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.List; + +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.Resource; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Base class for handling resource-contract relations. + * @param The resource type. + */ +@NoArgsConstructor +public abstract class AbstractResourceContractLinker + extends OwningRelationService, + ContractService> { + /** + * Get the list of contracts owned by the resource. + * @param owner The owner of the contracts. + * @return The list of owned contracts. + */ + @Override + protected List getInternal(final Resource owner) { + return owner.getContracts(); + } +} + +/** + * Handles the relation between an offered resource and its contracts. + */ +@Service +@NoArgsConstructor +class OfferedResourceContractLinker extends AbstractResourceContractLinker { } + +/** + * Handles the relation between a requested resource and its contracts. + */ +@Service +@NoArgsConstructor +class RequestedResourceContractLinker extends AbstractResourceContractLinker { } diff --git a/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceRepresentationLinker.java b/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceRepresentationLinker.java new file mode 100644 index 000000000..4f6ac1213 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/AbstractResourceRepresentationLinker.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.List; + +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.Resource; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Handles the relation between a resources and its representations. + * @param The resource type. + */ +@NoArgsConstructor +public abstract class AbstractResourceRepresentationLinker + extends OwningRelationService, + RepresentationService> { + /** + * Get the list of representations owned by the resource. + * @param owner The owner of the representations. + * @return The list of owned representations. + */ + @Override + protected List getInternal(final Resource owner) { + return owner.getRepresentations(); + } +} + +/** + * Handles the relation between an offered resource and its representations. + */ +@Service +@NoArgsConstructor +class OfferedResourceRepresentation extends AbstractResourceRepresentationLinker { +} + +/** + * Handles the relation between a requested resource and its representations. + */ +@Service +@NoArgsConstructor +class RequestedResourceRepresentation + extends AbstractResourceRepresentationLinker { } diff --git a/src/main/java/io/dataspaceconnector/services/resources/AgreementService.java b/src/main/java/io/dataspaceconnector/services/resources/AgreementService.java new file mode 100644 index 000000000..4cbf14f41 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/AgreementService.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.AgreementDesc; +import io.dataspaceconnector.repositories.AgreementRepository; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Handles the basic logic for contracts. + */ +@Service +@NoArgsConstructor +public class AgreementService extends BaseEntityService { + + /** + * Compares the agreement with the persisted one. If they are equal the agreement + * will be confirmed. + * @param agreement The agreement that should be confirmed. + * @return true - if the was unconfirmed and has been changed to confirmed. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the agreement does no longer exist. + */ + public boolean confirmAgreement(final Agreement agreement) { + final var persisted = this.get(agreement.getId()); + var isConfirmed = false; + if (persisted.equals(agreement)) { + final var repo = (AgreementRepository) getRepository(); + repo.confirmAgreement(agreement.getId()); + isConfirmed = true; + } + + return isConfirmed; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/ArtifactService.java b/src/main/java/io/dataspaceconnector/services/resources/ArtifactService.java new file mode 100644 index 000000000..8dbda344e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/ArtifactService.java @@ -0,0 +1,373 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.model.ArtifactImpl; +import io.dataspaceconnector.model.LocalData; +import io.dataspaceconnector.model.QueryInput; +import io.dataspaceconnector.model.RemoteData; +import io.dataspaceconnector.repositories.ArtifactRepository; +import io.dataspaceconnector.repositories.DataRepository; +import io.dataspaceconnector.services.ArtifactRetriever; +import io.dataspaceconnector.services.HttpService; +import io.dataspaceconnector.services.usagecontrol.PolicyVerifier; +import io.dataspaceconnector.services.usagecontrol.VerificationResult; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import kotlin.Pair; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Handles the basic logic for artifacts. + */ +@Log4j2 +@Service +public class ArtifactService extends BaseEntityService + implements RemoteResolver { + /** + * Repository for storing data. + **/ + private final @NonNull DataRepository dataRepo; + + /** + * Service for http communication. + **/ + private final @NonNull HttpService httpSvc; + + /** + * Constructor for ArtifactService. + * + * @param dataRepository The data repository. + * @param httpService The HTTP service for fetching remote data. + */ + @Autowired + public ArtifactService(final @NonNull DataRepository dataRepository, + final @NonNull HttpService httpService) { + this.dataRepo = dataRepository; + this.httpSvc = httpService; + } + + /** + * Persist the artifact and its data. + * + * @param artifact The artifact to persists. + * @return The persisted artifact. + */ + @Override + protected Artifact persist(final Artifact artifact) { + final var tmp = (ArtifactImpl) artifact; + if (tmp.getData() != null) { + if (tmp.getData().getId() == null) { + // The data element is new, insert + dataRepo.saveAndFlush(tmp.getData()); + } else { + // The data element exists already, check if an update is + // required + final var storedCopy = dataRepo.getOne(tmp.getData().getId()); + if (!storedCopy.equals(tmp.getData())) { + dataRepo.saveAndFlush(tmp.getData()); + } + } + + if (tmp.getData() instanceof LocalData) { + final var factory = ((ArtifactFactory) getFactory()); + factory.updateByteSize(artifact, ((LocalData) tmp.getData()).getValue()); + } + } + + return super.persist(tmp); + } + + /** + * Get the artifacts data. If agreements for this resource exist, all of them will be tried for + * data access. + * + * @param accessVerifier Checks if the data access should be allowed. + * @param retriever Retrieves the data from an external source. + * @param artifactId The id of the artifact. + * @param queryInput The query for the backend. + * @return The artifacts data. + * @throws PolicyRestrictionException if the data access has been denied. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the artifact does not exist. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Transactional + public InputStream getData(final PolicyVerifier accessVerifier, + final ArtifactRetriever retriever, final UUID artifactId, + final QueryInput queryInput) throws PolicyRestrictionException { + + // TODO: Parameter Null checks + + /* + * NOTE: Check if agreements with remoteIds are set for this artifact. If such agreements + * exist the artifact must be assigned to a requested resource. The data access should + * now be treated from the perspective of the data consumer. Since no knowledge which + * agreement applies has been passed we need to query the database for all viable agreements + * and try accessing the data till one of them returns the data. If none of them returns + * the data it means all data access has been forbidden. Do not proceed. + */ + final var agreements = + ((ArtifactRepository) getRepository()).findRemoteOriginAgreements(artifactId); + if (agreements.size() > 0) { + var policyException = new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + for (final var agRemoteId : agreements) { + try { + final var info = new RetrievalInformation(agRemoteId, null, queryInput); + return getData(accessVerifier, retriever, artifactId, info); + } catch (PolicyRestrictionException exception) { + // Access denied, log it and try the next agreement. + if (log.isDebugEnabled()) { + log.debug("Tried to access artifact data by trying an agreement. " + + "[artifactId=({}), agreementId=({})]", + artifactId, agRemoteId); + } + + policyException = exception; + } + } + + // All attempts on accessing data failed. Deny access with the last rejection reason. + if (log.isDebugEnabled()) { + log.debug("The requested resource is not owned by this connector." + + " Access forbidden. [artifactId=({})]", artifactId); + } + + throw policyException; + } + + // The artifact is not assigned to any requested resources. It must be offered if it exists. + return getDataFromInternalDB((ArtifactImpl) get(artifactId), queryInput); + } + + /** + * Get data restricted by a contract. If the data is not available an artifact requests will + * pull the data. + * + * @param accessVerifier Checks if the data access should be allowed. + * @param retriever Retrieves the data from an external source. + * @param artifactId The id of the artifact. + * @param information Information for pulling the data from a remote source. + * @return The artifact's data. + * @throws PolicyRestrictionException if the data access has been denied. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the artifact does not exist. + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Transactional + public InputStream getData(final PolicyVerifier accessVerifier, + final ArtifactRetriever retriever, final UUID artifactId, + final RetrievalInformation information) + throws PolicyRestrictionException { + + // TODO: Parameter Null checks + + // Check the artifact exists and access is granted. + final var artifact = get(artifactId); + if (accessVerifier.verify(artifact) == VerificationResult.DENIED) { + if (log.isInfoEnabled()) { + log.info("Access denied. [artifactId=({})]", artifactId); + } + + throw new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + } + + // Make sure the data exists and is up to date. + final var shouldDownload = shouldDownload(artifact, information.getForceDownload()); + if (shouldDownload) { + /* + NOTE: Make this not blocking. + */ + final var dataStream = retriever.retrieve(artifactId, + artifact.getRemoteAddress(), information.getTransferContract(), + information.getQueryInput()); + final var persistedData = setData(artifactId, dataStream); + artifact.incrementAccessCounter(); + persist(artifact); + return persistedData; + } + + // Artifact exists, access granted, data exists and data up to date. + return getDataFromInternalDB((ArtifactImpl) artifact, null); + } + + /** + * Get the data from the internal database. No policy enforcement is performed here! + * + * @param artifact The artifact which data should be returned. + * @param queryInput The query for the data backend. May be null. + * @return The artifact's data. + */ + private InputStream getDataFromInternalDB(final ArtifactImpl artifact, + final QueryInput queryInput) { + final var data = artifact.getData(); + + InputStream rawData; + if (data instanceof LocalData) { + rawData = getData((LocalData) data); + } else if (data instanceof RemoteData) { + rawData = getData((RemoteData) data, queryInput); + } else { + throw new UnreachableLineException("Unknown data type."); + } + + artifact.incrementAccessCounter(); + persist(artifact); + + return rawData; + } + + private boolean shouldDownload(final Artifact artifact, final Boolean forceDownload) { + if (forceDownload == null) { + // TODO: Add checks if the data is still up to date. This will remove unnecessary + // downloads. + return !isDataPresent() || artifact.isAutomatedDownload(); + } else { + return forceDownload; + } + } + + private boolean isDataPresent() { + // TODO: Check if the data has been downloaded at least once. + return false; + } + + /** + * Get local data. + * + * @param data The data container. + * @return The stored data. + */ + private InputStream getData(final LocalData data) { + return toInputStream(data.getValue()); + } + + /** + * Get remote data. + * + * @param data The data container. + * @param queryInput The query for the backend. + * @return The stored data. + */ + private InputStream getData(final RemoteData data, final QueryInput queryInput) { + try { + InputStream backendData; + if (data.getUsername() != null || data.getPassword() != null) { + backendData = httpSvc.get(data.getAccessUrl(), queryInput, + new Pair<>(data.getUsername(), data.getPassword())) + .getBody(); + } else { + backendData = httpSvc.get(data.getAccessUrl(), queryInput).getBody(); + } + + return backendData; + } catch (IOException exception) { + if (log.isWarnEnabled()) { + log.warn("Could not connect to data source. [exception=({})]", + exception.getMessage(), exception); + } + throw new RuntimeException("Could not connect to data source.", exception); + } + } + + /** + * Finds all artifacts referenced in a specific agreement. + * + * @param agreementId ID of the agreement + * @return list of all artifacts referenced in the agreement + */ + public List getAllByAgreement(final UUID agreementId) { + Utils.requireNonNull(agreementId, ErrorMessages.ENTITYID_NULL); + return ((ArtifactRepository) getRepository()).findAllByAgreement(agreementId); + } + + /** + * {@inheritDoc} + */ + @Override + public Optional identifyByRemoteId(final URI remoteId) { + final var repo = (ArtifactRepository) getRepository(); + return repo.identifyByRemoteId(remoteId); + } + + /** + * Update an artifacts underlying data. + * + * @param artifactId The artifact which should be updated. + * @param data The new data. + * @return The data stored in the artifact. + */ + @Transactional + public InputStream setData(final UUID artifactId, final InputStream data) { + var artifact = get(artifactId); + final var localData = ((ArtifactImpl) artifact).getData(); + if (localData instanceof LocalData) { + try { + /* + * NOTE: The service or the factories need to implement some form of patching. But + * since this is the only place where a single value is updated its enough to use a + * query for this. + */ + + // Update the internal database and return the new data. + final var bytes = data.readAllBytes(); + data.close(); + dataRepo.setLocalData(localData.getId(), bytes); + if (((ArtifactFactory) getFactory()).updateByteSize(artifact, bytes)) { + ((ArtifactRepository) getRepository()).setArtifactData(artifactId, + artifact.getCheckSum(), + artifact.getByteSize()); + } + + return new ByteArrayInputStream(bytes); + } catch (Exception e) { + if (log.isErrorEnabled()) { + log.error("Failed to store data. [artifactId=({}), exception=({})]", + artifactId, e.getMessage(), e); + } + + // TODO This needs to be an expected exception. At the moment this should be fine + // if the internal db in not reachable there will be a lot more problems. + // throw new IOException("Failed to store data"); + throw new RuntimeException("Failed to store data."); + } + } else { + // TODO Push data to remote backend. Missing concept. + throw new RuntimeException("Not implemented"); + } + } + + private InputStream toInputStream(final byte[] data) { + return new ByteArrayInputStream(data); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/BaseEntityService.java b/src/main/java/io/dataspaceconnector/services/resources/BaseEntityService.java new file mode 100644 index 000000000..fbb53251b --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/BaseEntityService.java @@ -0,0 +1,175 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.AbstractDescription; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.AbstractFactory; +import io.dataspaceconnector.repositories.BaseEntityRepository; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.UUID; + +/** + * The base service implements base logic for persistent entities. + * + * @param The entity type. + * @param The description for the passed entity type. + */ +public class BaseEntityService> { + /** + * Persists all entities of type T. + **/ + @Autowired + private BaseEntityRepository repository; + + /** + * Contains creation and update logic for entities of type T. + **/ + @Autowired + private AbstractFactory factory; + + /** + * Default constructor. + */ + protected BaseEntityService() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Creates a new persistent entity. + * + * @param desc The description of the new entity. + * @return The new entity. + * @throws IllegalArgumentException if the desc is null. + */ + public T create(final D desc) { + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + return persist(factory.create(desc)); + } + + /** + * Updates an existing entity. + * + * @param entityId The id of the entity. + * @param desc The new description of the entity. + * @return The updated entity. + * @throws IllegalArgumentException if any of the passed arguments is null. + * @throws ResourceNotFoundException if the entity is unknown. + */ + public T update(final UUID entityId, final D desc) { + Utils.requireNonNull(entityId, ErrorMessages.ENTITYID_NULL); + Utils.requireNonNull(desc, ErrorMessages.DESC_NULL); + + var entity = get(entityId); + + if (factory.update(entity, desc)) { + entity = persist(entity); + } + + return entity; + } + + /** + * Get the entity for a given id. + * + * @param entityId The id of the entity. + * @return The entity. + * @throws IllegalArgumentException if the passed id is null. + * @throws ResourceNotFoundException if the entity is unknown. + */ + public T get(final UUID entityId) { + Utils.requireNonNull(entityId, ErrorMessages.ENTITYID_NULL); + + final var entity = repository.findById(entityId); + + if (entity.isEmpty()) { + // Handle with global exception handler + throw new ResourceNotFoundException(this.getClass().getSimpleName() + ": " + entityId); + } + + return entity.get(); + } + + /** + * Get a list of all entities with of the same type. + * + * @param pageable Range selection of the complete data set. + * @return The id list of all entities. + * @throws IllegalArgumentException if the passed pageable is null. + */ + public Page getAll(final Pageable pageable) { + Utils.requireNonNull(pageable, ErrorMessages.PAGEABLE_NULL); + return repository.findAll(pageable); + } + + /** + * Checks if a entity exists for a given id. + * + * @param entityId The id of entity. + * @return True if the entity exists. + * @throws IllegalArgumentException if the passed id is null. + */ + public boolean doesExist(final UUID entityId) { + Utils.requireNonNull(entityId, ErrorMessages.ENTITYID_NULL); + return repository.findById(entityId).isPresent(); + } + + /** + * Delete an entity with the given id. + * + * @param entityId The id of the entity. + * @throws IllegalArgumentException if the passed id is null. + */ + public void delete(final UUID entityId) { + Utils.requireNonNull(entityId, ErrorMessages.ENTITYID_NULL); + repository.deleteById(entityId); + } + + /** + * Persists an entity. + * + * @param entity The entity. + * @return The persisted entity. + */ + protected T persist(final T entity) { + return repository.saveAndFlush(entity); + } + + /** + * Returns the repository so it can be accessed in subclasses. + * + * @return the repository + */ + protected BaseEntityRepository getRepository() { + return repository; + } + + /** + * Returns the factory so it can be accessed in subclasses. + * + * @return the factory + */ + protected AbstractFactory getFactory() { + return factory; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/CatalogService.java b/src/main/java/io/dataspaceconnector/services/resources/CatalogService.java new file mode 100644 index 000000000..dc3665857 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/CatalogService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; + +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Handles the basic logic for catalogs. + */ +@Service +@NoArgsConstructor +public class CatalogService extends BaseEntityService { +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/ContractService.java b/src/main/java/io/dataspaceconnector/services/resources/ContractService.java new file mode 100644 index 000000000..e253bfbd4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/ContractService.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.repositories.ContractRepository; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +/** + * Handles the basic logic for contracts. + */ +@Service +@NoArgsConstructor +public class ContractService extends BaseEntityService { + + /** + * Finds all contracts applicable for a specific artifact. + * + * @param artifactId ID of the artifact + * @return list of contracts applicable for the artifact + */ + public List getAllByArtifactId(final UUID artifactId) { + Utils.requireNonNull(artifactId, ErrorMessages.ENTITYID_NULL); + return ((ContractRepository) getRepository()).findAllByArtifactId(artifactId); + } + +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/EntityDependencyResolver.java b/src/main/java/io/dataspaceconnector/services/resources/EntityDependencyResolver.java new file mode 100644 index 000000000..f1f8d052b --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/EntityDependencyResolver.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.net.URI; +import java.util.List; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.utils.EndpointUtils; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * This service offers methods for finding entities related to another given entity. + */ +@Service +@RequiredArgsConstructor +public class EntityDependencyResolver { + + /** + * Service for persisting and querying contracts. + */ + private final @NonNull ContractService contractService; + + /** + * Service for persisting and querying rules. + */ + private final @NonNull RuleService ruleService; + + /** + * Service for persisting and querying artifacts. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Gets all contracts applicable for a specific artifact. + * + * @param artifactId The artifact id. + * @return List of contract offers. + */ + public List getContractOffersByArtifactId(final URI artifactId) { + final var uuid = EndpointUtils.getUUIDFromPath(artifactId); + Utils.requireNonNull(artifactId, ErrorMessages.ENTITYID_NULL); + return contractService.getAllByArtifactId(uuid); + } + + /** + * Finds all rules in a specific contract. + * + * @param contract the contract + * @return list of all rules in the contract + */ + public List getRulesByContractOffer(final Contract contract) { + Utils.requireNonNull(contract, ErrorMessages.ENTITY_NULL); + return ruleService.getAllByContract(contract.getId()); + } + + /** + * Gets all artifacts referenced in a specific agreement. + * + * @param agreement the agreement + * @return list of all artifacts referenced in the agreement + */ + public List getArtifactsByAgreement(final Agreement agreement) { + Utils.requireNonNull(agreement, ErrorMessages.ENTITY_NULL); + return artifactService.getAllByAgreement(agreement.getId()); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/NonOwningRelationService.java b/src/main/java/io/dataspaceconnector/services/resources/NonOwningRelationService.java new file mode 100644 index 000000000..97d58c457 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/NonOwningRelationService.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import io.dataspaceconnector.model.AbstractEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; + +/* + NOTE: All entities in our model use n to m relationships. Due to the use of JPA + only one entity can be the owner of the relationship and only the owner may persist + changes to the relationship. This means when both sides should be capable of persisting + the CASCADING Annotation needs to be used. Here lies the problem. When getting an entity + and all related entities you could modify its children. With the missing setter only + the Factories are capable of changing but you could still change the children. And + the decision if an entity should be persisted should only be with the Service responsible + for the specific entity. + Due to this the ownership of the relationship leaks into the class design. The upside + is that the leakage stops here and you only need to care about the different linker + if you are adding or removing relationships. + The following class is basically a proxy class calling the right RelationshipService. + */ + +/** + * Creates a parent-children relationship between two types of resources. + * Implements the non-owning side of a relationship. + * @param The type of the parent resource. + * @param The type of the child resource. (The owning side) + * @param The service type for the parent resource. + * @param The service type for the child resource. + */ +public abstract class NonOwningRelationService< + K extends AbstractEntity, + W extends AbstractEntity, + T extends BaseEntityService, + X extends BaseEntityService + > extends AbstractRelationService { + + /* + NOTE: For some reason Spring does not find the owningService when the services are + set. As long this does not create a problem, do not touch this. + */ + /** + * The service response for the inverse of this relation. + */ + @Autowired + private OwningRelationService owningService; + + @Override + protected final void addInternal(final UUID ownerId, final Set entities) { + final var set = Set.of(ownerId); + entities.forEach(id -> owningService.add(id, set)); + } + + @Override + public final void removeInternal(final UUID ownerId, final Set entities) { + final var set = Set.of(ownerId); + entities.stream().peek(id -> owningService.remove(id, set)).close(); + } + + @Override + public final void replaceInternal(final UUID ownerId, final Set entities) { + final var set = Set.of(ownerId); + final var allRelations = + getOneService().getAll(Pageable.unpaged()).stream().map(AbstractEntity::getId) + .collect(Collectors.toList()); + allRelations.stream().peek(id -> owningService.remove(id, set)).close(); + add(ownerId, entities); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/OfferedResourceService.java b/src/main/java/io/dataspaceconnector/services/resources/OfferedResourceService.java new file mode 100644 index 000000000..687368ca3 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/OfferedResourceService.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * Handles the basic logic for offered resources. + */ +@Service +@NoArgsConstructor +public class OfferedResourceService extends ResourceService { +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/OwningRelationService.java b/src/main/java/io/dataspaceconnector/services/resources/OwningRelationService.java new file mode 100644 index 000000000..8f19a5b75 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/OwningRelationService.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import io.dataspaceconnector.model.AbstractEntity; + +/** + * Creates a parent-children relationship between two types of resources. + * Implements the owning side of a relationship. + * @param The type of the parent resource. (The owning side) + * @param The type of the child resource. + * @param The service type for the parent resource. + * @param The service type for the child resource. + */ +public abstract class OwningRelationService< + K extends AbstractEntity, W extends AbstractEntity, T extends BaseEntityService, X + extends BaseEntityService> extends AbstractRelationService { + + @Override + protected final void addInternal(final UUID ownerId, final Set entities) { + final var owner = getOneService().get(ownerId); + addInternal(owner, entities); + getOneService().persist(owner); + } + + @Override + protected final void removeInternal(final UUID ownerId, final Set entities) { + final var owner = getOneService().get(ownerId); + final var existingEntities = getInternal(owner); + + for (final var entityId : entities) { + existingEntities.removeIf(x -> x.getId().equals(entityId)); + } + getOneService().persist(owner); + } + + @Override + protected final void replaceInternal(final UUID ownerId, final Set entities) { + final var owner = getOneService().get(ownerId); + getInternal(owner).clear(); + addInternal(owner, entities); + getOneService().persist(owner); + } + + /** + * Adds children to an entity. + * @param owner The entity that the children should be assigned to. + * @param entities The children added to the entity. + */ + protected void addInternal(final K owner, final Set entities) { + final var existingEntities = getInternal(owner); + final var existingIds = + existingEntities.parallelStream().map(W::getId).collect(Collectors.toSet()); + + final var toBeAdded = new HashSet<>(entities); + toBeAdded.removeAll(existingIds); + + for (final var entityId : toBeAdded) { + final var entity = getManyService().get(entityId); + existingEntities.add(entity); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RelationService.java b/src/main/java/io/dataspaceconnector/services/resources/RelationService.java new file mode 100644 index 000000000..1baeeae33 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RelationService.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.Set; +import java.util.UUID; + +import io.dataspaceconnector.model.AbstractEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Creates and modifies relations between two entity types. + * + * @param Type of owner entity. + * @param Type of children entity. + * @param Type of owning entity service. + * @param Type of child entity service. + */ +public interface RelationService, X extends BaseEntityService> { + + /** + * Get all children of an entity. + * + * @param ownerId The id of the entity whose children should be received. + * @param pageable The {@link Pageable} object for getting only a page of objects. + * @return The ids of the children. + * @throws IllegalArgumentException if any of the passed arguments is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if the ownerId entity does not exists. + */ + Page get(UUID ownerId, Pageable pageable); + + /** + * Add a list of children to an entity. The children must exist. + * + * @param ownerId The id of the entity that the children should be added to. + * @param entities The children to be added. + * @throws IllegalArgumentException if any of the passed arguments is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if any of the entities does not exists. + */ + void add(UUID ownerId, Set entities); + + /** + * Remove a list of children from an entity. + * + * @param ownerId The id of the entity that the children should be removed from. + * @param entities The children to be removed. + * @throws IllegalArgumentException if any of the passed arguments is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if any of the entities does not exists. + */ + void remove(UUID ownerId, Set entities); + + /** + * Replace the children of an entity. + * + * @param ownerId The id of the entity whose children should be replaced. + * @param entities The new children for the entity. + * @throws IllegalArgumentException if any of the passed arguments is null. + * @throws io.dataspaceconnector.exceptions.ResourceNotFoundException + * if any of the entities does not exists. + */ + void replace(UUID ownerId, Set entities); +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RelationServices.java b/src/main/java/io/dataspaceconnector/services/resources/RelationServices.java new file mode 100644 index 000000000..1318b22a8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RelationServices.java @@ -0,0 +1,220 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.List; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * This class contains all implementations of {@link OwningRelationService} and + * {@link NonOwningRelationService}. + */ +public final class RelationServices { + + /** + * Handles the relation between rules and contracts. + */ + @Service + @NoArgsConstructor + public static class RuleContractLinker extends NonOwningRelationService { + + @Override + protected final List getInternal(final ContractRule owner) { + return owner.getContracts(); + } + } + + /** + * Handles the relation between artifacts and representations. + */ + @Service + @NoArgsConstructor + public static class ArtifactRepresentationLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Artifact owner) { + return owner.getRepresentations(); + } + } + + /** + * Handles the relation between representations and offered resources. + */ + @Service + @NoArgsConstructor + public static class RepresentationOfferedResourceLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Representation owner) { + return (List) (List) owner.getResources(); + } + } + + /** + * Handles the relation between representations and requested resources. + */ + @Service + @NoArgsConstructor + public static class RepresentationRequestedResourceLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Representation owner) { + return (List) (List) owner.getResources(); + } + } + + /** + * Handles the relation between offered resources and catalogs. + */ + @Service + @NoArgsConstructor + public static class OfferedResourceCatalogLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final OfferedResource owner) { + return owner.getCatalogs(); + } + } + + /** + * Handles the relation between requested resources and catalogs. + */ + @Service + @NoArgsConstructor + public static class RequestedResourceCatalogLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final RequestedResource owner) { + return owner.getCatalogs(); + } + } + + /** + * Handles the relation between contracts and offered resources. + */ + @Service + @NoArgsConstructor + public static class ContractOfferedResourceLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Contract owner) { + return (List) (List) owner.getResources(); + } + } + + /** + * Handles the relation between contracts and requested resources. + */ + @Service + @NoArgsConstructor + public static class ContractRequestedResourceLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Contract owner) { + return (List) (List) owner.getResources(); + } + } + + /** + * Handles the relation between agreements and artifacts. + */ + @Service + @NoArgsConstructor + public static class AgreementArtifactLinker + extends OwningRelationService { + + @Override + protected final List getInternal(final Agreement owner) { + return owner.getArtifacts(); + } + } + + /** + * Handles the relation between artifacts and agreements. + */ + @Service + @NoArgsConstructor + public static class ArtifactAgreementLinker + extends NonOwningRelationService { + + @Override + protected final List getInternal(final Artifact owner) { + return owner.getAgreements(); + } + } + + /** + * Handles the relation between representations and artifacts. + */ + @Service + @NoArgsConstructor + public static class RepresentationArtifactLinker extends OwningRelationService { + /** + * Get the list of artifacts owned by the representation. + * @param owner The owner of the artifacts. + * @return The list of owned artifacts. + */ + @Override + protected List getInternal(final Representation owner) { + return owner.getArtifacts(); + } + } + + /** + * Handles the relation between contracts and rules. + */ + @Service + @NoArgsConstructor + public static class ContractRuleLinker extends OwningRelationService { + /** + * Get the list of rules owned by the contract. + * @param owner The owner of the rules. + * @return The list of owned rules. + */ + @Override + protected List getInternal(final Contract owner) { + return owner.getRules(); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RemoteResolver.java b/src/main/java/io/dataspaceconnector/services/resources/RemoteResolver.java new file mode 100644 index 000000000..ee203e743 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RemoteResolver.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +/** + * Resolves an remote address to an local entity. + */ +public interface RemoteResolver { + /** + * Search for an local entity by its remote id. + * @param remoteId The remote id. + * @return The local entity id. + */ + Optional identifyByRemoteId(URI remoteId); +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RepresentationService.java b/src/main/java/io/dataspaceconnector/services/resources/RepresentationService.java new file mode 100644 index 000000000..48688f4dd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RepresentationService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.repositories.RepresentationRepository; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +/** + * Service for managing representations. + */ +@Service +public final class RepresentationService extends BaseEntityService implements RemoteResolver { + + @Override + public Optional identifyByRemoteId(final URI remoteId) { + final var repo = (RepresentationRepository) getRepository(); + return repo.identifyByRemoteId(remoteId); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RequestedResourceService.java b/src/main/java/io/dataspaceconnector/services/resources/RequestedResourceService.java new file mode 100644 index 000000000..1441bfe95 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RequestedResourceService.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.repositories.RequestedResourcesRepository; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +/** + * Handles the basic logic for requested resources. + */ +@Service +@NoArgsConstructor +public final class RequestedResourceService extends ResourceService implements RemoteResolver { + /** + * {@inheritDoc} + */ + @Override + public Optional identifyByRemoteId(final URI remoteId) { + final var repo = (RequestedResourcesRepository) getRepository(); + return repo.identifyByRemoteId(remoteId); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/ResourceService.java b/src/main/java/io/dataspaceconnector/services/resources/ResourceService.java new file mode 100644 index 000000000..3da0e3328 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/ResourceService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Resource; +import io.dataspaceconnector.model.ResourceDesc; +import lombok.NoArgsConstructor; + +/** + * Handles the basic logic for resources. + * + * @param The resource type. + * @param The resource description type. + */ +@NoArgsConstructor +public class ResourceService> + extends BaseEntityService { +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RetrievalInformation.java b/src/main/java/io/dataspaceconnector/services/resources/RetrievalInformation.java new file mode 100644 index 000000000..6c0ee8a1a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RetrievalInformation.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.net.URI; + +import io.dataspaceconnector.model.QueryInput; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + + +/** + * Bundles information for retrieving data from another connector. + */ +@AllArgsConstructor +@Data +@RequiredArgsConstructor +public class RetrievalInformation { + /** + * The transferContract with which the data transfer is authorized. + */ + private @NonNull URI transferContract; + + /** + * If the data should be downloaded. + * null - Let the connector decide. + * true - Always download. + * false - Do not download the data under any condition. + */ + private Boolean forceDownload; + + /** + * Query option for limiting the scope of the data pulled. + * The query input may be ignored by some connectors. + */ + private QueryInput queryInput; +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/RuleService.java b/src/main/java/io/dataspaceconnector/services/resources/RuleService.java new file mode 100644 index 000000000..96c607f97 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/RuleService.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.repositories.RuleRepository; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +/** + * Handles the basic logic for contract rules. + */ +@Service +@NoArgsConstructor +public class RuleService extends BaseEntityService { + + /** + * Finds all rules in a specific contract. + * + * @param contractId ID of the contract + * @return list of all rules in the contract + */ + public List getAllByContract(final UUID contractId) { + Utils.requireNonNull(contractId, ErrorMessages.ENTITYID_NULL); + return ((RuleRepository) getRepository()).findAllByContract(contractId); + } + +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/TemplateBuilder.java b/src/main/java/io/dataspaceconnector/services/resources/TemplateBuilder.java new file mode 100644 index 000000000..6d437f3a9 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/TemplateBuilder.java @@ -0,0 +1,311 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.stream.Collectors; + +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.Resource; +import io.dataspaceconnector.model.ResourceDesc; +import io.dataspaceconnector.model.templates.ArtifactTemplate; +import io.dataspaceconnector.model.templates.ContractTemplate; +import io.dataspaceconnector.model.templates.RepresentationTemplate; +import io.dataspaceconnector.model.templates.ResourceTemplate; +import io.dataspaceconnector.model.templates.RuleTemplate; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.Utils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Builds and links entities from templates. + * @param The resource type. + * @param The resource description type. + */ +@RequiredArgsConstructor +public abstract class TemplateBuilder> { + + /** + * The service for resources. + */ + private final @NonNull ResourceService resourceService; + + /** + * The linker for resource-representation relations. + */ + private final @NonNull AbstractResourceRepresentationLinker resourceRepresentationLinker; + + /** + * The linker for resource-contract relations. + */ + private final @NonNull AbstractResourceContractLinker resourceContractLinker; + + /** + * The service for representations. + */ + private final @NonNull RepresentationService representationService; + + /** + * The linker for representation-artifact relations. + */ + private final @NonNull + RelationServices.RepresentationArtifactLinker representationArtifactLinker; + + /** + * The service for contracts. + */ + private final @NonNull ContractService contractService; + + /** + * The linker for contract-rule relations. + */ + private final @NonNull RelationServices.ContractRuleLinker contractRuleLinker; + + /** + * The service for artifacts. + */ + @Autowired + private ArtifactService artifactService; + + /** + * The service for rules. + */ + @Autowired + private RuleService ruleService; + + /** + * Build a resource and dependencies from a template. + * @param template The resource template. + * @return The new resource. + * @throws IllegalArgumentException if the passed template is null. + */ + public T build(final ResourceTemplate template) { + Utils.requireNonNull(template, ErrorMessages.ENTITY_NULL); + + final var representationIds = + Utils.toStream(template.getRepresentations()).map(x -> build(x).getId()) + .collect(Collectors.toSet()); + final var contractIds = Utils.toStream(template.getContracts()).map(x -> build(x).getId()) + .collect(Collectors.toSet()); + final var resource = buildResource(template); + + resourceRepresentationLinker.add(resource.getId(), representationIds); + resourceContractLinker.add(resource.getId(), contractIds); + + return resource; + } + + /** + * Creates a resource from a resource template. + * + * @param template the template. + * @return the resource. + */ + protected abstract T buildResource(ResourceTemplate template); + + /** + * Build a representation and dependencies from template. + * @param template The representation template. + * @return The new representation. + * @throws IllegalArgumentException if the passed template is null. + */ + public Representation build(final RepresentationTemplate template) { + Utils.requireNonNull(template, ErrorMessages.ENTITY_NULL); + + final var artifactIds = Utils.toStream(template.getArtifacts()).map(x -> build(x).getId()) + .collect(Collectors.toSet()); + Representation representation; + final var repId = + representationService.identifyByRemoteId(template.getDesc().getRemoteId()); + if (repId.isPresent()) { + representation = representationService.update(repId.get(), template.getDesc()); + } else { + representation = representationService.create(template.getDesc()); + } + + representationArtifactLinker.add(representation.getId(), artifactIds); + + return representation; + } + + /** + * Build a contract and dependencies from a template. + * @param template The contract template. + * @return The new contract. + * @throws IllegalArgumentException if the passed template is null. + */ + public Contract build(final ContractTemplate template) { + Utils.requireNonNull(template, ErrorMessages.ENTITY_NULL); + + final var ruleIds = Utils.toStream(template.getRules()).map(x -> build(x).getId()) + .collect(Collectors.toSet()); + final var contract = contractService.create(template.getDesc()); + contractRuleLinker.add(contract.getId(), ruleIds); + + return contract; + } + + /** + * Build an artifact and dependencies from a template. + * @param template The artifact template. + * @return The new artifact. + * @throws IllegalArgumentException if the passed template is null. + */ + public Artifact build(final ArtifactTemplate template) { + Utils.requireNonNull(template, ErrorMessages.ENTITY_NULL); + + Artifact artifact; + final var contractId = artifactService.identifyByRemoteId(template.getDesc().getRemoteId()); + if (contractId.isPresent()) { + artifact = artifactService.update(contractId.get(), template.getDesc()); + } else { + artifact = artifactService.create(template.getDesc()); + } + + return artifact; + } + + /** + * Build a rule and dependencies from a template. + * @param template The rule template. + * @return The new rule. + * @throws IllegalArgumentException if the passed template is null. + */ + public ContractRule build(final RuleTemplate template) { + Utils.requireNonNull(template, ErrorMessages.ENTITY_NULL); + return ruleService.create(template.getDesc()); + } + + /** + * Return the resource service for subclasses. + * @return The resource service. + */ + protected ResourceService getResourceService() { + return resourceService; + } +} + + +/** + * Template builder for offered resources. + */ +@Service +final class TemplateBuilderOfferedResource + extends TemplateBuilder { + /** + * Default constructor. + * @param resourceService The resource service. + * @param resourceRepresentationLinker The resource-representation service. + * @param resourceContractLinker The resource-contract service. + * @param representationService The representation service. + * @param representationArtifactLinker The representation-artifact service. + * @param contractService The contract service. + * @param contractRuleLinker The contract-rule service. + */ + @Autowired + TemplateBuilderOfferedResource( + final ResourceService resourceService, + final AbstractResourceRepresentationLinker + resourceRepresentationLinker, + final AbstractResourceContractLinker resourceContractLinker, + final RepresentationService representationService, + final RelationServices.RepresentationArtifactLinker representationArtifactLinker, + final ContractService contractService, + final RelationServices.ContractRuleLinker contractRuleLinker) { + super(resourceService, resourceRepresentationLinker, resourceContractLinker, + representationService, representationArtifactLinker, contractService, + contractRuleLinker); + } + + @Override + protected OfferedResource buildResource(final ResourceTemplate template) { + return getResourceService().create(template.getDesc()); + } +} + + +/** + * Template builder for requested resources. + */ +@Service +final class TemplateBuilderRequestedResource + extends TemplateBuilder { + /** + * Default constructor. + * @param resourceService The resource service. + * @param resourceRepresentationLinker The resource-representation service. + * @param resourceContractLinker The resource-contract service. + * @param representationService The representation service. + * @param representationArtifactLinker The representation-artifact service. + * @param contractService The contract service. + * @param contractRuleLinker The contract-rule service. + */ + @Autowired + TemplateBuilderRequestedResource( + final ResourceService resourceService, + final AbstractResourceRepresentationLinker + resourceRepresentationLinker, + final AbstractResourceContractLinker resourceContractLinker, + final RepresentationService representationService, + final RelationServices.RepresentationArtifactLinker representationArtifactLinker, + final ContractService contractService, + final RelationServices.ContractRuleLinker contractRuleLinker) { + super(resourceService, resourceRepresentationLinker, resourceContractLinker, + representationService, representationArtifactLinker, contractService, + contractRuleLinker); + } + + @Override + protected RequestedResource buildResource( + final ResourceTemplate template) { + final var resourceService = getResourceService(); + + RequestedResource resource; + if (resourceService instanceof RemoteResolver) { + final var resourceId = ((RemoteResolver) resourceService) + .identifyByRemoteId(template.getOldRemoteId()); + if (resourceId.isPresent()) { + if (template.getOldRemoteId().equals(template.getDesc().getRemoteId())) { + resource = resourceService.update(resourceId.get(), template.getDesc()); + } else { + final var doesExist = ((RemoteResolver) resourceService) + .identifyByRemoteId(template.getDesc().getRemoteId()).isPresent(); + if (doesExist) { + throw new IllegalStateException(); + } else { + resource = resourceService.update(resourceId.get(), template.getDesc()); + } + } + + } else { + resource = resourceService.create(template.getDesc()); + } + } else { + resource = resourceService.create(template.getDesc()); + } + + return resource; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/resources/package-info.java b/src/main/java/io/dataspaceconnector/services/resources/package-info.java new file mode 100644 index 000000000..e1d00bcf4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/resources/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Services responsible for handling resources. + */ +package io.dataspaceconnector.services.resources; diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifier.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifier.java new file mode 100644 index 000000000..70d9d6ffe --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifier.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import io.dataspaceconnector.model.Artifact; +import org.springframework.stereotype.Component; + +/** + * A {@link PolicyVerifier} implementation that simply allows access. + */ +@Component +public final class AllowAccessVerifier implements PolicyVerifier { + @Override + public VerificationResult verify(final Artifact input) { + return VerificationResult.ALLOWED; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/ContractManager.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/ContractManager.java new file mode 100644 index 000000000..fb45f8cce --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/ContractManager.java @@ -0,0 +1,217 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractAgreementBuilder; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.ContractRequestBuilder; +import de.fraunhofer.iais.eis.Duty; +import de.fraunhofer.iais.eis.DutyImpl; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.PermissionImpl; +import de.fraunhofer.iais.eis.Prohibition; +import de.fraunhofer.iais.eis.ProhibitionImpl; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.ids.framework.util.IDSUtils; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.MessageResponseException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.resources.EntityDependencyResolver; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.RuleUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +/** + * This service offers methods related to contract management. + */ +@Log4j2 +@Service +@RequiredArgsConstructor +public class ContractManager { + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Service for resolving elements and its parents/children. + */ + private final @NonNull EntityDependencyResolver dependencyResolver; + + /** + * Service for resolving entities. + */ + private final @NonNull EntityResolver entityResolver; + + /** + * Service for current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Check if the transfer contract is valid and the conditions are fulfilled. + * + * @param agreementId The id of the contract. + * @param requestedArtifact The id of the artifact. + * @return The contract agreement on successful validation. + * @throws IllegalArgumentException if contract agreement deserialization fails. + * @throws ResourceNotFoundException if agreement could not be found. + * @throws ContractException if the contract agreement does not match the requested + * artifact or is not confirmed. + */ + public ContractAgreement validateTransferContract( + final URI agreementId, final URI requestedArtifact) throws IllegalArgumentException, + ResourceNotFoundException, ContractException { + final var agreement = entityResolver.getAgreementByUri(agreementId); + final var artifacts = dependencyResolver.getArtifactsByAgreement(agreement); + + final var valid = ContractUtils.isMatchingTransferContract(artifacts, requestedArtifact); + // TODO Add validation of issuer connector. + if (!valid) { + // If the requested artifact does not match the agreement, send rejection message. + throw new ContractException("Transfer contract does not match the requested artifact."); + } + + // Negotiation has to be finished to make the agreement valid. + if (!agreement.isConfirmed()) { + throw new ContractException("Contract agreement has not been confirmed. Send contract " + + "agreement message to finish the negotiation sequence."); + } + + return deserializationService.getContractAgreement(agreement.getValue()); + } + + /** + * Read and validate ids contract agreement from ids response message. + * + * @param payload The message's payload (agreement as string). + * @param request The contract request that was sent. + * @return The ids contract agreement. + * @throws MessageResponseException If the response could not be processed. + * @throws IllegalArgumentException If deserialization fails. + * @throws ContractException If the contract's content is invalid. + */ + public ContractAgreement validateContractAgreement( + final String payload, final ContractRequest request) throws MessageResponseException, + IllegalArgumentException, ContractException { + final var agreement = deserializationService.getContractAgreement(payload); + + ContractUtils.validateRuleAssigner(agreement); + RuleUtils.validateRuleContent(request, agreement); + + return agreement; + } + + /** + * Build contract request from a list of rules - with assignee and consumer. + * + * @param ruleList The rule list. + * @return The ids contract request. + * @throws ConstraintViolationException If ids contract building fails. + */ + public ContractRequest buildContractRequest(final List ruleList) + throws ConstraintViolationException { + final var connectorId = connectorService.getConnectorId(); + + final var permissions = new ArrayList(); + final var prohibitions = new ArrayList(); + final var obligations = new ArrayList(); + + // Add assignee to all rules. + for (final var rule : ruleList) { + if (rule instanceof Permission) { + ((PermissionImpl) rule).setAssignee(Util.asList(connectorId)); + permissions.add((Permission) rule); + } else if (rule instanceof Prohibition) { + ((ProhibitionImpl) rule).setAssignee(Util.asList(connectorId)); + prohibitions.add((Prohibition) rule); + } else if (rule instanceof Duty) { + ((DutyImpl) rule).setAssignee(Util.asList(connectorId)); + obligations.add((Duty) rule); + } + } + + // Return contract request. + return new ContractRequestBuilder() + ._consumer_(connectorId) + ._obligation_(obligations) + ._permission_(permissions) + ._prohibition_(prohibitions) + .build(); + } + + /** + * Build contract agreement from contract request. Sign all rules as assigner. + * + * @param request The contract request. + * @param id ID to use when creating the contract agreement. + * @param issuer The issuer connector id. + * @return The contract agreement. + * @throws ConstraintViolationException If building a contract agreement fails. + */ + public ContractAgreement buildContractAgreement( + final ContractRequest request, final URI id, final URI issuer) + throws ConstraintViolationException { + final var connectorId = connectorService.getConnectorId(); + + final var ruleList = ContractUtils.extractRulesFromContract(request); + + final var permissions = new ArrayList(); + final var prohibitions = new ArrayList(); + final var obligations = new ArrayList(); + + // Add assigner to all rules. + for (final var rule : ruleList) { + if (rule instanceof Permission) { + ((PermissionImpl) rule).setAssigner(Util.asList(connectorId)); + permissions.add((Permission) rule); + } else if (rule instanceof Prohibition) { + ((ProhibitionImpl) rule).setAssigner(Util.asList(connectorId)); + prohibitions.add((Prohibition) rule); + } else if (rule instanceof Duty) { + ((DutyImpl) rule).setAssigner(Util.asList(connectorId)); + obligations.add((Duty) rule); + } + } + + // Return contract request. + return new ContractAgreementBuilder(id) + ._consumer_(issuer) + ._contractDate_(IDSUtils.getGregorianNow()) + ._contractStart_(IDSUtils.getGregorianNow()) + ._contractEnd_(request.getContractEnd()) // TODO Improve calculation of contract + // end. + ._obligation_(obligations) + ._permission_(permissions) + ._prohibition_(prohibitions) + ._provider_(connectorId) + .build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/DataAccessVerifier.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/DataAccessVerifier.java new file mode 100644 index 000000000..e5aa6c0b7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/DataAccessVerifier.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.exceptions.PolicyExecutionException; +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.RuleUtils; +import io.dataspaceconnector.utils.SelfLinkHelper; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +/** + * A {@link PolicyVerifier} implementation that checks whether data access should be allowed. + */ +@Component +@Log4j2 +@RequiredArgsConstructor +public final class DataAccessVerifier implements PolicyVerifier { + + /** + * The policy execution point. + */ + private final @NonNull RuleValidator ruleValidator; + + /** + * Service for configuring policy settings. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Service for resolving entities. + */ + private final @NonNull EntityResolver entityResolver; + + /** + * Policy check on data access on consumer side. Ignore if unknown patterns are allowed. + * + * @param target The requested element. + * @throws PolicyRestrictionException If a policy restriction has been detected. + */ + public void checkPolicy(final Artifact target) throws PolicyRestrictionException { + final var patternsToCheck = Arrays.asList( + PolicyPattern.PROVIDE_ACCESS, + PolicyPattern.USAGE_DURING_INTERVAL, + PolicyPattern.USAGE_UNTIL_DELETION, + PolicyPattern.DURATION_USAGE, + PolicyPattern.USAGE_LOGGING, + PolicyPattern.N_TIMES_USAGE, + PolicyPattern.USAGE_NOTIFICATION); + + try { + final var artifactId = SelfLinkHelper.getSelfLink(target); + checkForAccess(patternsToCheck, artifactId, target.getRemoteId()); + } catch (PolicyRestrictionException exception) { + // Unknown patterns cause an exception. Ignore if unsupported patterns are allowed. + if (!connectorConfig.isAllowUnsupported()) { + throw exception; + } + } + } + + /** + * Checks the contract content for data access (on consumer side). + * + * @param patterns List of patterns that should be enforced. + * @param artifactId The requested artifact. + * @param remoteId The remote id of the requested artifact. + * @throws io.dataspaceconnector.exceptions.UnsupportedPatternException + * If no suitable pattern could be found. + */ + public void checkForAccess(final List patterns, final URI artifactId, + final URI remoteId) { + // Get the contract agreement's rules for the target. + final var agreements = entityResolver.getContractAgreementsByTarget(artifactId); + for (final var agreement : agreements) { + final var rules = ContractUtils.getRulesForTargetId(agreement, remoteId); + + // Check the policy of each rule. + for (final var rule : rules) { + final var pattern = RuleUtils.getPatternByRule(rule); + // Enforce only a set of patterns. + if (patterns.contains(pattern)) { + ruleValidator.validatePolicy(pattern, rule, artifactId, null); + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public VerificationResult verify(final Artifact input) { + try { + this.checkPolicy(input); + return VerificationResult.ALLOWED; + } catch (PolicyRestrictionException exception) { + if (log.isDebugEnabled()) { + log.debug("Data access denied. [input=({})]", input, exception); + } + return VerificationResult.DENIED; + } catch (PolicyExecutionException e) { + // If message could not be sent, ignore and provide access anyway. + if (log.isDebugEnabled()) { + log.debug("[exception=({}), input=({})]", e.getMessage(), input, e); + } + return VerificationResult.ALLOWED; + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/DataProvisionVerifier.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/DataProvisionVerifier.java new file mode 100644 index 000000000..a26ff2686 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/DataProvisionVerifier.java @@ -0,0 +1,117 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import de.fraunhofer.iais.eis.ContractAgreement; +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.RuleUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +/** + * A {@link PolicyVerifier} implementation that checks whether data provision should be allowed. + */ +@Component +@Log4j2 +@RequiredArgsConstructor +public class DataProvisionVerifier implements PolicyVerifier { + + /** + * The policy execution point. + */ + private final @NonNull RuleValidator ruleValidator; + + /** + * Service for configuring policy settings. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Policy check on data provision on provider side. + * + * @param target The requested element. + * @param issuerConnector The issuer connector. + * @param agreement The ids contract agreement. + * @throws PolicyRestrictionException If a policy restriction has been detected. + */ + public void checkPolicy(final URI target, + final URI issuerConnector, + final ContractAgreement agreement) throws PolicyRestrictionException { + final var patternsToCheck = Arrays.asList( + PolicyPattern.PROVIDE_ACCESS, + PolicyPattern.PROHIBIT_ACCESS, + PolicyPattern.USAGE_DURING_INTERVAL, + PolicyPattern.USAGE_UNTIL_DELETION, + PolicyPattern.CONNECTOR_RESTRICTED_USAGE); + try { + checkForAccess(patternsToCheck, target, issuerConnector, agreement); + } catch (PolicyRestrictionException exception) { + // Unknown patterns cause an exception. Ignore if unsupported patterns are allowed. + if (!connectorConfig.isAllowUnsupported()) { + throw exception; + } + } + } + + /** + * Checks the contract content for data access (on provider side). + * + * @param patterns List of patterns that should be enforced. + * @param target The requested element. + * @param issuerConnector The issuer connector. + * @param agreement The ids contract agreement. + * @throws PolicyRestrictionException If a policy restriction has been detected. + */ + public void checkForAccess(final List patterns, + final URI target, final URI issuerConnector, + final ContractAgreement agreement) + throws PolicyRestrictionException { + final var rules = ContractUtils.getRulesForTargetId(agreement, target); + + // Check the policy of each rule. + for (final var rule : rules) { + final var pattern = RuleUtils.getPatternByRule(rule); + // Enforce only a set of patterns. + if (patterns.contains(pattern)) { + ruleValidator.validatePolicy(pattern, rule, target, issuerConnector); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public VerificationResult verify(final VerificationInput input) { + try { + this.checkPolicy(input.getTarget(), input.getIssuerConnector(), input.getAgreement()); + return VerificationResult.ALLOWED; + } catch (PolicyRestrictionException exception) { + if (log.isDebugEnabled()) { + log.debug("Data access denied. [input=({})]", input, exception); + } + return VerificationResult.DENIED; + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyExecutionService.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyExecutionService.java new file mode 100644 index 000000000..8647a5fdd --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyExecutionService.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.Rule; +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.exceptions.PolicyExecutionException; +import io.dataspaceconnector.exceptions.RdfBuilderException; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.messages.types.LogMessageService; +import io.dataspaceconnector.services.messages.types.NotificationService; +import io.dataspaceconnector.utils.IdsUtils; +import io.dataspaceconnector.utils.RuleUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Executes policy conditions. Refers to the ids policy enforcement point (PEP). + */ +@Service +@RequiredArgsConstructor +@Log4j2 +public class PolicyExecutionService { + + /** + * Service for configuring policy settings. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Service for the current connector configuration. + */ + private final @NonNull ConnectorService connectorService; + + /** + * Service for ids notification messages. + */ + private final @NonNull NotificationService notificationService; + + /** + * Service for ids log messages. + */ + private final @NonNull LogMessageService logMessageService; + + /** + * Send contract agreement to clearing house. + * + * @param agreement The ids contract agreement. + */ + public void sendAgreement(final ContractAgreement agreement) { + try { + final var rdf = IdsUtils.toRdf(agreement); + final var recipient = connectorConfig.getClearingHouse(); + if (!recipient.equals(URI.create(""))) { + logMessageService.sendMessage(recipient, rdf); + } + } catch (PolicyExecutionException | RdfBuilderException exception) { + if (log.isWarnEnabled()) { + log.warn("Failed to send contract agreement to clearing house. " + + "[exception=({})]", exception.getMessage()); + } + } + } + + /** + * Send a message to the clearing house. Allow the access only if that operation was successful. + * + * @param target The target object. + * @throws PolicyExecutionException if the access could not be successfully logged. + */ + public void logDataAccess(final URI target) throws PolicyExecutionException { + final var recipient = connectorConfig.getClearingHouse(); + final var logItem = buildLog(target).toString(); + + if (!recipient.equals(URI.create(""))) { + logMessageService.sendMessage(recipient, logItem); + } + } + + /** + * Send a message to the clearing house. Allow the access only if that operation was successful. + * + * @param rule The ids rule. + * @param element The accessed element. + * @throws PolicyExecutionException If the notification has not been successful. + */ + public void reportDataAccess(final Rule rule, final URI element) + throws PolicyExecutionException { + final var postDuty = ((Permission) rule).getPostDuty().get(0); + final var recipient = RuleUtils.getEndpoint(postDuty); + + final var logItem = buildLog(element).toString(); + + notificationService.sendMessage(URI.create(recipient), logItem); + } + + /** + * Build a log information object. + * + * @param target The accessed element. + * @return The log line. + */ + private Map buildLog(final URI target) { + final var id = connectorService.getConnectorId(); + + return new HashMap<>() {{ + put("target", target); + put("issuerConnector", id); + put("accessed", new Date()); + }}; + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyInformationService.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyInformationService.java new file mode 100644 index 000000000..2790453c1 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyInformationService.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.utils.EndpointUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.time.ZonedDateTime; + +/** + * This class provides access permission information for the {@link RuleValidator} depending on + * the policy content. + */ +@Service +@RequiredArgsConstructor +public class PolicyInformationService { + + /** + * Service for handling artifacts. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Get creation date of artifact. + * + * @param target The target id. + * @return The artifact's creation date. + */ + public ZonedDateTime getCreationDate(final URI target) { + final var resourceId = EndpointUtils.getUUIDFromPath(target); + final var artifact = artifactService.get(resourceId); + + return artifact.getCreationDate(); + } + + /** + * Get access number of artifact. + * + * @param target The target id. + * @return The artifact's access number. + */ + public long getAccessNumber(final URI target) { + final var resourceId = EndpointUtils.getUUIDFromPath(target); + final var artifact = artifactService.get(resourceId); + + return artifact.getNumAccessed(); + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyPattern.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyPattern.java new file mode 100644 index 000000000..8292a8a49 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyPattern.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +/** + * Enum describing policy patterns supported by this ids connector. + */ +public enum PolicyPattern { + + /** + * Standard pattern to allow unrestricted access. + */ + PROVIDE_ACCESS, + + /** + * Default pattern if no other is detected. + */ + PROHIBIT_ACCESS, + + /** + * Use the data not more than N times. + */ + N_TIMES_USAGE, + + /** + * Restrict the data usage to a specific time duration. + */ + DURATION_USAGE, + + /** + * Restrict the data usage to a specific time interval. + */ + USAGE_DURING_INTERVAL, + + /** + * Use data and delete it at a specific date time. + */ + USAGE_UNTIL_DELETION, + + /** + * Log the data usage information. + */ + USAGE_LOGGING, + + /** + * Notify a party or a specific group of users when the data is used. + */ + USAGE_NOTIFICATION, + + /** + * Restrict the data usage to specific connectors. + */ + CONNECTOR_RESTRICTED_USAGE +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyVerifier.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyVerifier.java new file mode 100644 index 000000000..cf7233eae --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/PolicyVerifier.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +/** + * Interface for verifying policies. + * + * @param Type for the verification input. + */ +public interface PolicyVerifier { + + /** + * Verify policy base on input. + * + * @param input Reference object of verification. + * @return The verification result. + */ + VerificationResult verify(T input); +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/RuleValidator.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/RuleValidator.java new file mode 100644 index 000000000..c08be91de --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/RuleValidator.java @@ -0,0 +1,276 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import de.fraunhofer.iais.eis.Rule; +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.TimeInterval; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.resources.EntityDependencyResolver; +import io.dataspaceconnector.utils.ErrorMessages; +import io.dataspaceconnector.utils.RuleUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.text.ParseException; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This class provides policy pattern recognition and calls the {@link + * PolicyInformationService} on data + * request or access. Refers to the ids policy decision point (PDP). + */ +@Service +@RequiredArgsConstructor +@Log4j2 +public class RuleValidator { + + /** + * Policy execution point. + */ + private final @NonNull PolicyExecutionService executionService; + + /** + * Policy information point. + */ + private final @NonNull PolicyInformationService informationService; + + /** + * Service for resolving elements and its parents/children. + */ + private final @NonNull EntityDependencyResolver dependencyResolver; + + /** + * Service for deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Validates the data access for a given rule. + * + * @param pattern The recognized policy pattern. + * @param rule The ids rule. + * @param target The requested/accessed element. + * @param issuerConnector The issuer connector. + * @throws PolicyRestrictionException If a policy restriction was detected. + */ + void validatePolicy(final PolicyPattern pattern, final Rule rule, final URI target, + final URI issuerConnector) throws PolicyRestrictionException { + switch (pattern) { + case PROVIDE_ACCESS: + break; + case USAGE_DURING_INTERVAL: + case USAGE_UNTIL_DELETION: + validateInterval(rule); + break; + case DURATION_USAGE: + validateDuration(rule, target); + break; + case USAGE_LOGGING: + executionService.logDataAccess(target); + break; + case N_TIMES_USAGE: + validateAccessNumber(rule, target); + break; + case USAGE_NOTIFICATION: + executionService.reportDataAccess(rule, target); + break; + case CONNECTOR_RESTRICTED_USAGE: + validateIssuerConnector(rule, issuerConnector); + break; + case PROHIBIT_ACCESS: + throw new PolicyRestrictionException(ErrorMessages.NOT_ALLOWED); + default: + if (log.isDebugEnabled()) { + log.debug("No pattern detected. [target=({})]", target); + } + throw new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + } + } + + /** + * Compare content of rule offer and request with each other. + * + * @param contractOffers The contract offer. + * @param map The target contract map. + * @param target The target value. + * @return True if everything is fine, false in case of mismatch. + */ + public boolean validateRulesOfRequest(final List contractOffers, + final Map> map, + final URI target) { + boolean valid = false; + for (final var contract : contractOffers) { + // Get rule list from contract offer. + final var ruleList = dependencyResolver.getRulesByContractOffer(contract); + // Get rule list from contract request. + final var values = map.get(target); + + // Compare rules + if (compareRulesOfOfferToRequest(ruleList, values)) { + valid = true; + break; + } + } + + return valid; + } + + /** + * Compare rule list of a contract offer to the rule list of a contract request. + * + * @param offerRules List of ids rules. + * @param requestRules List of ids rules. + * @return True if the lists are equal, false if not. + */ + private boolean compareRulesOfOfferToRequest(final List offerRules, + final List requestRules) { + final var idsRuleList = new ArrayList(); + for (final var rule : offerRules) { + final var value = rule.getValue(); + final var idsRule = deserializationService.getRule(value); + idsRuleList.add(idsRule); + } + + if (!RuleUtils.compareRules(idsRuleList, (ArrayList) requestRules)) { + if (log.isDebugEnabled()) { + log.debug("Rules do not match. [offer=({}), request=({})]", idsRuleList, + requestRules); + } + return false; + } + + return true; + } + + /** + * Checks if the requested data access is in the allowed time interval. + * + * @param rule The ids rule. + * @throws PolicyRestrictionException If the policy could not be read or a restriction is + * detected. + */ + private void validateInterval(final Rule rule) throws PolicyRestrictionException { + TimeInterval timeInterval; + try { + timeInterval = RuleUtils.getTimeInterval(rule); + } catch (ParseException e) { + if (log.isWarnEnabled()) { + log.warn("Could not read time interval. [exception=({})]", e.getMessage()); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_INTERVAL, e); + } + + final var current = RuleUtils.getCurrentDate(); + if (!current.isAfter(timeInterval.getStart()) || !current.isBefore(timeInterval.getEnd())) { + if (log.isWarnEnabled()) { + log.warn("Invalid time interval. [timeInterval=({})]", timeInterval); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_INTERVAL); + } + } + + /** + * Adds a duration to a given date and checks if the duration has already been exceeded. + * + * @param rule The ids rule. + * @param target The accessed element. + * @throws PolicyRestrictionException If the policy could not be read or a restriction is + * detected. + */ + private void validateDuration(final Rule rule, final URI target) + throws PolicyRestrictionException { + final var created = informationService.getCreationDate(target); + + final Duration duration; + try { + duration = RuleUtils.getDuration(rule); + } catch (DateTimeParseException e) { + if (log.isWarnEnabled()) { + log.warn("Could not read duration. [target=({}), exception=({})]", + target, e.getMessage(), e); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_INTERVAL, e); + } + + if (duration == null) { + if (log.isWarnEnabled()) { + log.warn("Duration is null. [target=({})]", target); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_INTERVAL); + } + + final var maxTime = RuleUtils.getCalculatedDate(created, duration); + final var validDate = RuleUtils.checkDate(RuleUtils.getCurrentDate(), maxTime); + + if (!validDate) { + if (log.isDebugEnabled()) { + log.debug("Invalid date time. [target=({})]", target); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_INTERVAL); + } + } + + /** + * Checks whether the maximum number of accesses has already been reached. + * + * @param rule The ids rule. + * @param target The accessed element. + * @throws PolicyRestrictionException If the access number has been reached. + */ + private void validateAccessNumber(final Rule rule, final URI target) + throws PolicyRestrictionException { + final var max = RuleUtils.getMaxAccess(rule); + // final var endpoint = PolicyUtils.getPipEndpoint(rule); + // NOTE: might be used later + + final var accessed = informationService.getAccessNumber(target); + if (accessed >= max) { + if (log.isDebugEnabled()) { + log.debug("Access number reached. [target=({})]", target); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_NUMBER_REACHED); + } + } + + /** + * Checks whether the requesting connector corresponds to the allowed connector. + * + * @param rule The ids rule. + * @param issuerConnector The issuer connector. + * @throws PolicyRestrictionException If the connector ids do no match. + */ + private void validateIssuerConnector(final Rule rule, final URI issuerConnector) + throws PolicyRestrictionException { + final var allowedConsumer = RuleUtils.getEndpoint(rule); + final var allowedConsumerAsUri = URI.create(allowedConsumer); + if (!allowedConsumerAsUri.equals(issuerConnector)) { + if (log.isDebugEnabled()) { + log.debug("Invalid consumer connector. [issuer=({})]", issuerConnector); + } + throw new PolicyRestrictionException(ErrorMessages.DATA_ACCESS_INVALID_CONSUMER); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/ScheduledDataRemoval.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/ScheduledDataRemoval.java new file mode 100644 index 000000000..f32b12a41 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/ScheduledDataRemoval.java @@ -0,0 +1,130 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.config.UsageControlFramework; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.utils.ContractUtils; +import io.dataspaceconnector.utils.RuleUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.net.URI; +import java.time.format.DateTimeParseException; + +/** + * This class implements automated policy check. + */ +@EnableScheduling +@Log4j2 +@RequiredArgsConstructor +@Service +public class ScheduledDataRemoval { + + /** + * The delay of the scheduler. + */ + private static final int FIXED_DELAY = 60_000; + + /** + * Service for configuring policy settings. + */ + private final @NonNull ConnectorConfiguration connectorConfig; + + /** + * Service for ids deserialization. + */ + private final @NonNull DeserializationService deserializationService; + + /** + * Service for ids deserialization. + */ + private final @NonNull AgreementService agreementService; + + /** + * Service for updating artifacts. + */ + private final @NonNull ArtifactService artifactService; + + /** + * Periodically checks agreements for data deletion. + */ + @Scheduled(fixedDelay = FIXED_DELAY) + public void schedule() { + try { + if (connectorConfig.getUcFramework() == UsageControlFramework.INTERNAL) { + if (log.isInfoEnabled()) { + log.info("Scanning agreements..."); + } + scanAgreements(); + } + } catch (IllegalArgumentException | DateTimeParseException | ResourceNotFoundException e) { + if (log.isWarnEnabled()) { + log.warn("Failed to check policy. [exception=({})]", e.getMessage()); + } + } + } + + /** + * Checks all known agreements for artifacts that have to be deleted. + * + * @throws DateTimeParseException If a date from a policy cannot be parsed. + * @throws IllegalArgumentException If the rule could not be deserialized. + * @throws ResourceNotFoundException If the data could not be deleted. + */ + private void scanAgreements() throws DateTimeParseException, IllegalArgumentException, + ResourceNotFoundException { + for (final var agreement : agreementService.getAll(Pageable.unpaged())) { + final var value = agreement.getValue(); + final var idsAgreement = deserializationService.getContractAgreement(value); + final var rules = ContractUtils.extractRulesFromContract(idsAgreement); + for (final var rule : rules) { + final var delete = RuleUtils.checkRuleForPostDuties(rule); + if (delete) { + final var target = rule.getTarget(); + removeDataFromArtifact(target); + } + } + } + } + + /** + * Delete data by artifact id. + * + * @param target The artifact id. + * @throws ResourceNotFoundException If the artifact update fails. + */ + private void removeDataFromArtifact(final URI target) throws ResourceNotFoundException { + final var artifactId = artifactService.identifyByRemoteId(target); + if (artifactId.isPresent()) { + // Update data for artifact. + artifactService.setData(artifactId.get(), InputStream.nullInputStream()); + if (log.isDebugEnabled()) { + log.debug("Removed data from artifact. [target=({})]", artifactId); + } + } + } +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationInput.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationInput.java new file mode 100644 index 000000000..081365a03 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationInput.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import java.net.URI; + +import de.fraunhofer.iais.eis.ContractAgreement; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * A DTO for information required to decide if data provision should be allowed. + */ +@AllArgsConstructor +@Data +@RequiredArgsConstructor +public class VerificationInput { + + /** + * The id of the targeted artifact. + */ + private URI target; + + /** + * The id of the issuing connector. + */ + private URI issuerConnector; + + /** + * The contract agreements for policy verification. + */ + private ContractAgreement agreement; +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationResult.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationResult.java new file mode 100644 index 000000000..d1fac80d8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/VerificationResult.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +/** + * This class provides an enum for data usage verification result. + */ +public enum VerificationResult { + + /** + * Indicates that the data access is allowed. + */ + ALLOWED, + + /** + * Indicates that the data access is denied. + */ + DENIED +} diff --git a/src/main/java/io/dataspaceconnector/services/usagecontrol/package-info.java b/src/main/java/io/dataspaceconnector/services/usagecontrol/package-info.java new file mode 100644 index 000000000..c71a20174 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/services/usagecontrol/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Services for performing usage control. + */ +package io.dataspaceconnector.services.usagecontrol; diff --git a/src/main/java/io/dataspaceconnector/utils/BasePath.java b/src/main/java/io/dataspaceconnector/utils/BasePath.java new file mode 100644 index 000000000..5457ed5d4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/BasePath.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import io.dataspaceconnector.exceptions.UnreachableLineException; + +/** + * The list of the api's paths. + */ +public enum BasePath { + + /** + * The resource offer's endpoint's base path. + */ + OFFERS("/api/offers"), + + /** + * The resource request's endpoint's base path. + */ + REQUESTS("/api/requests"), + + /** + * The representation endpoint's base path. + */ + REPRESENTATIONS("/api/representations"), + + /** + * The contract endpoint's base path. + */ + CONTRACTS("/api/contracts"), + + /** + * The artifact endpoint's base path. + */ + ARTIFACTS("/api/artifacts"), + + /** + * The rule endpoint's base path. + */ + RULES("/api/rules"), + + /** + * The catalog endpoint's base path. + */ + CATALOGS("/api/catalogs"), + + /** + * The contract agreement's base path. + */ + AGREEMENTS("/api/agreements"); + + /** + * The path as string. + */ + private final String basePath; + + BasePath(final String path) { + this.basePath = path; + } + + @Override + public String toString() { + final var host = EndpointUtils.getCurrentBasePathString(); + return host + basePath; + } + + /** + * Convert string to enum. + * + * @param path The base path as string. + * @return The base path as enum. + */ + public static BasePath fromString(final String path) { + for (final var b : BasePath.values()) { + if (path.contains(b.basePath)) { + return b; + } + } + + throw new UnreachableLineException("This code should not have been reached."); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/ContractUtils.java b/src/main/java/io/dataspaceconnector/utils/ContractUtils.java new file mode 100644 index 000000000..0e226a489 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/ContractUtils.java @@ -0,0 +1,212 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.Rule; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Artifact; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static io.dataspaceconnector.utils.RuleUtils.compareObligations; +import static io.dataspaceconnector.utils.RuleUtils.comparePermissions; +import static io.dataspaceconnector.utils.RuleUtils.compareProhibitions; + +/** + * Contains utility methods for creating and validation ids contracts. + */ +public final class ContractUtils { + + /** + * Constructor without params. + */ + private ContractUtils() { + // not used + } + + /** + * Return all contract rules as one list. + * + * @param contract The ids contract. + * @return A list of ids rules. + * @throws IllegalArgumentException If the message is null. + */ + public static List extractRulesFromContract(final Contract contract) { + Utils.requireNonNull(contract, ErrorMessages.CONTRACT_NULL); + final var permissionList = contract.getPermission(); + final var ruleList = permissionList == null ? new ArrayList() + : new ArrayList(permissionList); + + final var prohibitionList = contract.getProhibition(); + if (prohibitionList != null) { + ruleList.addAll(prohibitionList); + } + + final var obligationList = contract.getObligation(); + if (obligationList != null) { + ruleList.addAll(obligationList); + } + + return ruleList; + } + + /** + * Iterate over all rules of a contract and add the ones with the element as their target to a + * rule list. + * + * @param contract The contract. + * @param element The requested element. + * @return List of ids rules. + * @throws IllegalArgumentException If the message is null. + */ + public static List getRulesForTargetId(final Contract contract, + final URI element) { + Utils.requireNonNull(contract, ErrorMessages.CONTRACT_NULL); + final var rules = new ArrayList(); + + for (final var permission : contract.getPermission()) { + final var target = permission.getTarget(); + if (target != null && target.equals(element)) { + rules.add(permission); + } + } + + for (final var prohibition : contract.getProhibition()) { + final var target = prohibition.getTarget(); + if (target != null && target.equals(element)) { + rules.add(prohibition); + } + } + + for (final var obligation : contract.getObligation()) { + final var target = obligation.getTarget(); + if (target != null && target.equals(element)) { + rules.add(obligation); + } + } + + return rules; + } + + /** + * Extract targets and save them together with the respective rules to a map. + * + * @param rules List of ids rules. + * @return Map with targets and matching rules. + */ + public static Map> getTargetRuleMap(final List rules) { + final var targetRuleMap = new HashMap>(); + + // Iterate over all rules. + for (final var rule : rules) { + // Get target of rule. + final var target = rule.getTarget(); + + // If the target is already in the map, add the rule to the value list. + if (targetRuleMap.containsKey(target)) { + final var value = targetRuleMap.get(target); + value.add(rule); + } else { + // If not, create a target-rule-entry to the map. + final var value = new ArrayList(); + value.add(rule); + targetRuleMap.put(target, value); + } + } + + return targetRuleMap; + } + + /** + * Check if contract offer has a restricted consumer. If the value does not match the issuer + * connector, remove the contract from the list. + * + * @param issuerConnector The requesting consumer. + * @param contracts List of contracts. + * @return Cleaned list of contracts. + * @throws IllegalArgumentException if any of the arguments is null. + */ + public static List removeContractsWithInvalidConsumer( + final List contracts, + final URI issuerConnector) { + Utils.requireNonNull(contracts, ErrorMessages.LIST_NULL); + Utils.requireNonNull(issuerConnector, ErrorMessages.URI_NULL); + + return contracts.parallelStream() + .filter(x -> x.getConsumer().equals(issuerConnector) || x.getConsumer() + .toString() + .isBlank()) + .collect(Collectors.toList()); + } + + /** + * Check if the transfer contract's target matches the requested artifact. + * + * @param artifacts List of artifacts. + * @param requestedArtifact Id of the requested artifact. + * @return True if the requested artifact matches the transfer contract's artifacts. + * @throws ResourceNotFoundException If a resource could not be found. + */ + public static boolean isMatchingTransferContract(final List artifacts, + final URI requestedArtifact) + throws ResourceNotFoundException { + for (final var artifact : artifacts) { + final var endpoint = SelfLinkHelper.getSelfLink(artifact); + if (endpoint.equals(requestedArtifact)) { + return true; + } + } + + // If the requested artifact could not be found in the transfer contract (agreement). + return false; + } + + /** + * Validate if the assigner is the expected one. + * + * @param agreement The contract agreement. + * @throws ContractException If the assigner is not as expected. + */ + public static void validateRuleAssigner(final ContractAgreement agreement) + throws ContractException { + // TODO implement later + // NOTE: Recipient url might not be the connector id. + } + + /** + * Compare two contract agreements to each other. + * + * @param consumer The consumer agreement. + * @param provider The provider agreement. + * @return True if both agreements are equal. + * @throws ContractException If both objects do not match. + */ + public static boolean compareContractAgreements(final ContractAgreement consumer, + final ContractAgreement provider) { + return consumer.getId().equals(provider.getId()) + && comparePermissions(consumer.getPermission(), provider.getPermission()) + && compareProhibitions(consumer.getProhibition(), provider.getProhibition()) + && compareObligations(consumer.getObligation(), provider.getObligation()); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/ControllerUtils.java b/src/main/java/io/dataspaceconnector/utils/ControllerUtils.java new file mode 100644 index 000000000..33f760cb7 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/ControllerUtils.java @@ -0,0 +1,255 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.net.URI; +import java.util.Map; + +import lombok.extern.log4j.Log4j2; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * Contains utility methods for creating ResponseEntities with different status codes and custom + * messages or exceptions. + */ +@Log4j2 +public final class ControllerUtils { + + /** + * Default constructor. + */ + private ControllerUtils() { + // not used + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that an error occurred + * in the ids communication. + * + * @param exception Exception that was thrown during communication. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondIdsMessageFailed(final Exception exception) { + if (log.isDebugEnabled()) { + log.debug("Ids message handling failed. [exception=({})]", exception.getMessage(), + exception); + } + return new ResponseEntity<>("Ids message handling failed. " + exception.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that an error occurred + * in the ids communication. + * + * @param exception Exception that was thrown during communication. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondReceivedInvalidResponse(final Exception exception) { + if (log.isDebugEnabled()) { + log.debug("Received invalid ids response. [exception=({})]", + exception.getMessage(), exception); + } + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that the configuration + * could not be updated. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondConfigurationUpdateError( + final Exception exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to update the configuration. [exception=({})]", + exception.getMessage(), exception); + } + return new ResponseEntity<>("Failed to update configuration.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 400 and a message indicating that an input could + * not be deserialized. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 400. + */ + public static ResponseEntity respondDeserializationError(final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Failed to deserialize the object. [exception=({})]", + exception.getMessage(), exception); + } + return new ResponseEntity<>("Failed to update.", HttpStatus.BAD_REQUEST); + } + + /** + * Creates a ResponseEntity with status code 404 and a message indicating that the no + * configuration could be found. + * + * @return ResponseEntity with status code 404. + */ + public static ResponseEntity respondConfigurationNotFound() { + if (log.isInfoEnabled()) { + log.info("No configuration could be found."); + } + return new ResponseEntity<>("No configuration found.", HttpStatus.NOT_FOUND); + } + + /** + * Creates a ResponseEntity with status code 404 and a message indicating that a resource could + * not be found. + * + * @param resourceId ID for that no match was found. + * @return ResponseEntity with status code 404. + */ + public static ResponseEntity respondResourceNotFound(final URI resourceId) { + if (log.isDebugEnabled()) { + log.debug("The resource does not exist. [resourceId=({})]", resourceId); + } + return new ResponseEntity<>(String.format("Resource %s not found.", resourceId), + HttpStatus.NOT_FOUND); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that a resource could + * not be loaded. + * + * @param resourceId ID of the resource. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondResourceCouldNotBeLoaded(final URI resourceId) { + if (log.isDebugEnabled()) { + log.debug("Resource not loaded. [resourceId=({})]", resourceId); + } + return new ResponseEntity<>(String.format("Could not load resource %s.", resourceId), + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that no predefined + * policy pattern has been recognized. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondPatternNotIdentified(final Exception exception) { + if (log.isDebugEnabled()) { + log.debug("Failed to identify policy pattern.", exception); + } + return new ResponseEntity<>("Could not identify pattern.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that the input was + * invalid. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondInvalidInput(final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Failed to deserialize the input. [exception=({})]", + exception.getMessage(), exception); + } + return new ResponseEntity<>("Invalid input. " + exception.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that the contract + * request could not be built. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondFailedToBuildContractRequest( + final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Failed to build contract request. [exception=({})]", + exception.getMessage(), exception); + } + return new ResponseEntity<>("Failed to build contract request.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that the connector + * could not be loaded or deserialized. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondConnectorNotLoaded(final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Connector could not be loaded. [exception=({})]", exception.getMessage(), + exception); + } + return new ResponseEntity<>("Connector could not be loaded.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that something went + * wrong. Note: Should never be thrown. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondGlobalException(final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Something else went wrong. [exception=({})]", exception.getMessage(), + exception); + } + return new ResponseEntity<>("Something else went wrong.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that saving an entity + * has failed. + * + * @param exception The exception that was thrown. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondFailedToStoreEntity(final Exception exception) { + if (log.isWarnEnabled()) { + log.warn("Failed to store entity. [exception=({})]", exception.getMessage(), + exception); + } + return new ResponseEntity<>("Failed to store entity. " + exception.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Show response message that was not expected. + * + * @param response The response map. + * @return ResponseEntity with status code 417. + */ + public static ResponseEntity respondWithMessageContent( + final Map response) { + if (log.isWarnEnabled()) { + log.warn("Expectation failed. [response=({})]", response); + } + return new ResponseEntity<>(response, HttpStatus.EXPECTATION_FAILED); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/EndpointUtils.java b/src/main/java/io/dataspaceconnector/utils/EndpointUtils.java new file mode 100644 index 000000000..c72031637 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/EndpointUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.EndpointId; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; +import java.util.UUID; + +/** + * Contains utility methods for processing path and entity IDs. + */ +@Log4j2 +public final class EndpointUtils { + + /** + * Default constructor. + */ + private EndpointUtils() { + // not used + } + + /** + * Extracts base path and resource id from uri. + * + * @param uri The url. + * @return Endpoint containing base path and resource id (uuid). + * @throws IllegalArgumentException Failed to extract uuid from string. + */ + public static EndpointId getEndpointIdFromPath(final URI uri) throws IllegalArgumentException { + final var fullPath = uri.toString(); + final var allUuids = UUIDUtils.findUuids(fullPath); + + final var resourceId = UUID.fromString(allUuids.get(0)); + final var index = fullPath.lastIndexOf(resourceId.toString()) - 1; + // -1 so that the / gets also removed + final var basePath = fullPath.substring(0, index); + + return new EndpointId(basePath, resourceId); + } + + /** + * Get current base path as string. + * + * @return Base path as string. + */ + public static String getCurrentBasePathString() { + final var currentPath = EndpointUtils.getCurrentBasePath(); + return currentPath.toString().substring(0, + currentPath.toString().indexOf(currentPath.getPath())); + } + + /** + * Determines the current base path from the request context. + * + * @return The base path as uri. + */ + private static URI getCurrentBasePath() { + return getCurrentRequestUriBuilder().build().toUri(); + } + + /** + * Builds servlet uri from request context. + * + * @return The servlet uri component builder. + */ + private static ServletUriComponentsBuilder getCurrentRequestUriBuilder() { + return ServletUriComponentsBuilder.fromCurrentRequest(); + } + + /** + * Get base path enum from base path string. + * + * @param path The base path as string. + * @return The type of base path. + */ + public static BasePath getBasePathEnumFromString(final String path) { + try { + return BasePath.fromString(path); + } catch (UnreachableLineException exception) { + return null; + } + } + + /** + * Extract uuid from path url. + * + * @param url The url. + * @return The extracted uuid. + */ + public static UUID getUUIDFromPath(final URI url) { + try { + final var endpoint = EndpointUtils.getEndpointIdFromPath(url); + return endpoint.getResourceId(); + } catch (IllegalArgumentException e) { + if (log.isDebugEnabled()) { + log.debug("Could not retrieve uuid from path. [exception=({})]", e.getMessage()); + } + return null; + } + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/ErrorMessages.java b/src/main/java/io/dataspaceconnector/utils/ErrorMessages.java new file mode 100644 index 000000000..155a39ab6 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/ErrorMessages.java @@ -0,0 +1,207 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +/** + * Contains a list of common error messages used by exceptions. + */ +public enum ErrorMessages { + + /** + * The passed desc parameter may not be null. + */ + DESC_NULL("The description parameter may not be null."), + + /** + * The passed message may not be null. + */ + MESSAGE_NULL("The message may not be null."), + + /** + * The passed contract may not be null. + */ + CONTRACT_NULL("The contract may not be null."), + + /** + * The passed entity id may not be null. + */ + ENTITYID_NULL("The entity id may not be null."), + + /** + * The passed entity may not be null. + */ + ENTITY_NULL("The entity may not be null."), + + /** + * The passed pageable parameter may not be null. + */ + PAGEABLE_NULL("The pageable parameter may not be null."), + + /** + * The passed set of entities may not be null. + */ + ENTITYSET_NULL("The set of entities may not be null."), + + /** + * The exception parameter may not be null. + */ + EXCEPTION_NULL("The exception parameter may not be null."), + + /** + * Missing IDS version. + */ + VERSION_NULL("The version must be set."), + + /** + * Rule is missing a target parameter. + */ + MISSING_TARGET("Missing target id in rules"), + + /** + * The passed list may not be null. + */ + LIST_NULL("The passed list may not be null."), + + /** + * The passed uri may not be null. + */ + URI_NULL("The passed uri may not be null"), + + /** + * The passed url may not be null. + */ + URL_NULL("The passed url may not be null"), + + /** + * The passed http arguments may not be null. + */ + HTTP_ARGS_NULL("The passed http arguments may not be null"), + + /** + * One of the contracts is empty. + */ + EMPTY_CONTRACT("Empty contracts cannot be compared."), + + /** + * If the content of two contracts is not equal. + */ + CONTRACT_MISMATCH("The contract's content do not match."), + + /** + * No behavior has been defined for the object type. + */ + UNKNOWN_TYPE("No behavior has been defined for this type."), + + /** + * Failed to read response message. + */ + INVALID_RESPONSE("Invalid ids response message."), + + /** + * Ids message building failed. + */ + HEADER_BUILD_FAILED("Ids message header could not be built."), + + /** + * Multipart message building failed. + */ + MESSAGE_BUILD_FAILED("Multipart message could not be built."), + + /** + * DAT in response ids header is invalid. + */ + INVALID_RESPONSE_DAT("Invalid DAT in incoming message."), + + /** + * Multipart message could not be sent. + */ + MESSAGE_NOT_SENT("Message could not be sent."), + + /** + * Header of multipart message is malformed. + */ + MALFORMED_HEADER("Malformed message header."), + + /** + * Payload of multipart message is malformed. + */ + MALFORMED_PAYLOAD("Malformed message payload."), + + /** + * Payload of multipart message is missing. + */ + MISSING_PAYLOAD("Missing message payload."), + + /** + * Entity is null. + */ + EMTPY_ENTITY("Element could not be found."), + + /** + * Failed to read rdf string from ids object. + */ + RDF_FAILED("Could not retrieve rdf string."), + + /** + * Number of data accesses has been reached. + */ + DATA_ACCESS_NUMBER_REACHED("Valid access number reached."), + + /** + * Data has been accessed in an invalid time interval. + */ + DATA_ACCESS_INVALID_INTERVAL("Data access in invalid time interval."), + + /** + * Data has been accessed by an invalid consumer. + */ + DATA_ACCESS_INVALID_CONSUMER("Data access by invalid consumer connector."), + + /** + * Application's base URL was retrieved without request context present. + */ + NO_REQUEST_CONTEXT("No request context present for extracting base URL."), + + /** + * Due to a prohibit access pattern, accessing the data is not allowed. + */ + NOT_ALLOWED("Access is not allowed."), + + /** + * A policy restriction has been detected. + */ + POLICY_RESTRICTION("Policy restriction detected."); + + + /** + * Holds the enums string. + */ + private final String value; + + /** + * Constructor. + * + * @param message The msg of the error message. + */ + ErrorMessages(final String message) { + this.value = message; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/IdsUtils.java b/src/main/java/io/dataspaceconnector/utils/IdsUtils.java new file mode 100644 index 000000000..b3b2d87af --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/IdsUtils.java @@ -0,0 +1,258 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.BaseConnector; +import de.fraunhofer.iais.eis.Catalog; +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractOffer; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import io.dataspaceconnector.exceptions.RdfBuilderException; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.TimeZone; + +/** + * + */ +public final class IdsUtils { + + /** + * Default constructor. + */ + private IdsUtils() { + // not used + } + + /** + * Get rdf string from instance of type {@link BaseConnector}. + * + * @param baseConnector The ids connector. + * @return The ids connector as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final BaseConnector baseConnector) throws RdfBuilderException { + try { + return baseConnector.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link Resource}. + * + * @param resource The ids resource. + * @return The ids resource as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final Resource resource) throws RdfBuilderException { + try { + return resource.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link Artifact}. + * + * @param artifact The ids artifact. + * @return The ids artifact as rdf string. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException + * If the response could not be extracted. + */ + public static String toRdf(final Artifact artifact) throws RdfBuilderException { + try { + return artifact.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link Representation}. + * + * @param representation The ids representation. + * @return The ids representation as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final Representation representation) throws RdfBuilderException { + try { + return representation.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link Catalog}. + * + * @param catalog The ids catalog. + * @return The ids catalog as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final Catalog catalog) throws RdfBuilderException { + try { + return catalog.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link ContractRequest}. + * + * @param request The ids contract request. + * @return The ids contract request as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final ContractRequest request) throws RdfBuilderException { + try { + return request.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link ContractOffer}. + * + * @param offer The ids contract offer. + * @return The ids contract offer as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final ContractOffer offer) throws RdfBuilderException { + try { + return offer.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link ContractAgreement}. + * + * @param agreement The ids contract agreement. + * @return The ids contract agreement as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final ContractAgreement agreement) throws RdfBuilderException { + try { + return agreement.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get rdf string from instance of type {@link Rule}. + * + * @param rule The ids rule. + * @return The ids rule as rdf string. + * @throws RdfBuilderException If the response could not be extracted. + */ + public static String toRdf(final Rule rule) throws RdfBuilderException { + try { + return rule.toRdf(); + } catch (Exception exception) { + throw new RdfBuilderException(ErrorMessages.RDF_FAILED); + } + } + + /** + * Get list of keywords as ids list of typed literals. + * + * @param keywords List of keywords. + * @param language The language. + * @return List of typed literal. + */ + public static List getKeywordsAsTypedLiteral(final List keywords, + final String language) { + final var idsKeywords = new ArrayList(); + for (final var keyword : keywords) { + idsKeywords.add(new TypedLiteral(keyword, language)); + } + + return idsKeywords; + } + + /** + * Convert string to ids language. + * + * @param language The language as string. + * @return The ids language object. + */ + public static Language getLanguage(final String language) { + switch (language.toLowerCase()) { + case "de": + return Language.DE; + case "en": + default: + return Language.EN; + } + } + + /** + * Get list of ids keywords as list of strings. + * If the passed list is null, an empty list is returned. + * @param keywords List of typed literals. + * @return List of strings. + */ + public static List getKeywordsAsString( + final ArrayList keywords) { + + final var list = new ArrayList(); + if (keywords != null) { + for (final var keyword : keywords) { + list.add(keyword.getValue()); + } + } + + return list; + } + + /** + * Converts a date to XMLGregorianCalendar format. + * + * @param date the date object. + * @return the XMLGregorianCalendar object or null. + */ + public static XMLGregorianCalendar getGregorianOf(final ZonedDateTime date) { + final var calendar = GregorianCalendar.from(date); + calendar.setTimeZone(TimeZone.getTimeZone(ZoneId.ofOffset("", ZoneOffset.UTC))); + try { + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + } catch (DatatypeConfigurationException exception) { + // Rethrow but do not register in function header + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/MappingUtils.java b/src/main/java/io/dataspaceconnector/utils/MappingUtils.java new file mode 100644 index 000000000..21f7e1995 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/MappingUtils.java @@ -0,0 +1,418 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.ConnectorEndpoint; +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.Rule; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.templates.ArtifactTemplate; +import io.dataspaceconnector.model.templates.ContractTemplate; +import io.dataspaceconnector.model.templates.RepresentationTemplate; +import io.dataspaceconnector.model.templates.ResourceTemplate; +import io.dataspaceconnector.model.templates.RuleTemplate; + +/** + * Maps ids resources to internal resources. + */ +public final class MappingUtils { + + /** + * Default constructor. + */ + private MappingUtils() { + // not used + } + + /** + * Map ids resource to connector resource. + * + * @param resource The ids resource. + * @return The connector resource. + * @throws IllegalArgumentException if the passed resource is null. + */ + public static ResourceTemplate fromIdsResource(final Resource resource) { + Utils.requireNonNull(resource, ErrorMessages.ENTITY_NULL); + + final var periodicity = resource.getAccrualPeriodicity(); + final var contentPart = resource.getContentPart(); + final var contentStandard = resource.getContentStandard(); + final var contentType = resource.getContentType(); + final var created = resource.getCreated(); + final var customLicense = resource.getCustomLicense(); + final var representation = resource.getDefaultRepresentation(); + final var description = resource.getDescription(); + final var resourceId = resource.getId(); + final var keywords = IdsUtils.getKeywordsAsString(resource.getKeyword()); + final var language = resource.getLanguage(); + final var modified = resource.getModified(); + final var publisher = resource.getPublisher(); + final var resourceEndpoint = resource.getResourceEndpoint(); + final var resourcePart = resource.getResourcePart(); + final var sample = resource.getSample(); + final var shapesGraph = resource.getShapesGraph(); + final var sovereign = resource.getSovereign(); + final var spatialCoverage = resource.getSpatialCoverage(); + final var standardLicense = resource.getStandardLicense(); + final var temporalCoverage = resource.getTemporalCoverage(); + final var temporalRes = resource.getTemporalResolution(); + final var theme = resource.getTheme(); + final var title = resource.getTitle(); + final var variant = resource.getVariant(); + final var version = resource.getVersion(); + + // Add additional properties to map. + final var additional = propertiesToAdditional(resource.getProperties()); + + if (periodicity != null) { + additional.put("ids:accrualPeriodicity", periodicity.toRdf()); + } + + if (contentPart != null) { + additional.put("ids:contentPart", contentPart.toString()); + } + + if (contentStandard != null) { + additional.put("ids:contentStandard", contentStandard.toString()); + } + + if (contentType != null) { + additional.put("ids:contentType", contentType.toRdf()); + } + + if (created != null) { + additional.put("ids:created", created.toXMLFormat()); + } + + if (customLicense != null) { + additional.put("ids:customLicense", customLicense.toString()); + } + + if (representation != null) { + additional.put("ids:defaultRepresentation", representation.toString()); + } + + if (modified != null) { + additional.put("ids:modified", modified.toXMLFormat()); + } + + if (resourceEndpoint != null) { + additional.put("ids:resourceEndpoint", resourceEndpoint.toString()); + } + + if (resourcePart != null) { + additional.put("ids:resourcePart", resourcePart.toString()); + } + + if (sample != null) { + additional.put("ids:sample", sample.toString()); + } + + if (shapesGraph != null) { + additional.put("ids:shapesGraph", shapesGraph.toString()); + } + + if (spatialCoverage != null) { + additional.put("ids:spatialCoverage", spatialCoverage.toString()); + } + + if (temporalCoverage != null) { + additional.put("ids:temporalCoverage", temporalCoverage.toString()); + } + + if (temporalRes != null) { + additional.put("ids:temporalResolution", temporalRes.toString()); + } + + if (theme != null) { + additional.put("ids:theme", theme.toString()); + } + + if (variant != null) { + additional.put("ids:variant", variant.toString()); + } + + if (version != null) { + additional.put("ids:version", version); + } + + final var desc = new RequestedResourceDesc(); + desc.setAdditional(additional); + desc.setRemoteId(resourceId); + desc.setKeywords(keywords); + desc.setPublisher(publisher); + desc.setLicence(standardLicense); + desc.setSovereign(sovereign); + + if (description != null) { + desc.setDescription(description.toString()); + } + + if (title != null) { + desc.setTitle(title.toString()); + } + + if (language != null) { + desc.setLanguage(language.toString()); + } + + if (resourceEndpoint != null) { + getFirstEndpointDocumentation(resourceEndpoint) + .ifPresent(desc::setEndpointDocumentation); + } + + return new ResourceTemplate<>(null, desc, null, null); + } + + /** + * Map ids representation to connector representation. + * + * @param representation The ids representation. + * @return The connector representation. + * @throws IllegalArgumentException if the passed representation is null. + */ + public static RepresentationTemplate fromIdsRepresentation( + final Representation representation) { + Utils.requireNonNull(representation, ErrorMessages.ENTITY_NULL); + + final var created = representation.getCreated(); + final var representationId = representation.getId(); + final var language = representation.getLanguage(); + final var mediaType = representation.getMediaType(); + final var modified = representation.getModified(); + final var standard = representation.getRepresentationStandard(); + final var shape = representation.getShapesGraph(); + + // Add additional properties to map. + final var additional = propertiesToAdditional(representation.getProperties()); + + if (created != null) { + additional.put("ids:created", created.toXMLFormat()); + } + if (modified != null) { + additional.put("ids:modified", modified.toXMLFormat()); + } + + if (shape != null) { + additional.put("ids:shapesGraph", String.valueOf(shape)); + } + + final var desc = new RepresentationDesc(); + desc.setAdditional(additional); + desc.setRemoteId(representationId); + + if (standard != null) { + desc.setStandard(String.valueOf(standard)); + } + + if (language != null) { + desc.setLanguage(language.toString()); + } + + if (mediaType != null) { + desc.setMediaType(mediaType.getFilenameExtension()); + } + + return new RepresentationTemplate(null, desc, null); + } + + /** + * Build template from ids artifact. + * + * @param artifact The ids artifact. + * @param download Whether the artifact will be downloaded automatically. + * @param remoteUrl The provider's url for receiving artifact request messages. + * @return The artifact template. + * @throws IllegalArgumentException if the passed artifact is null. + */ + public static ArtifactTemplate fromIdsArtifact(final Artifact artifact, + final boolean download, final URI remoteUrl) { + Utils.requireNonNull(artifact, ErrorMessages.ENTITY_NULL); + + final var artifactId = artifact.getId(); + final var byteSize = artifact.getByteSize(); + final var checksum = artifact.getCheckSum(); + final var created = artifact.getCreationDate(); + final var duration = artifact.getDuration(); + final var filename = artifact.getFileName(); + + // Add additional properties to map. + final var additional = propertiesToAdditional(artifact.getProperties()); + + if (byteSize != null) { + additional.put("ids:byteSize", byteSize.toString()); + } + + if (checksum != null) { + additional.put("ids:checkSum", checksum); + } + + if (created != null) { + additional.put("ids:creationDate", created.toXMLFormat()); + } + + if (duration != null) { + additional.put("ids:duration", duration.toString()); + } + + final var desc = new ArtifactDesc(); + desc.setAdditional(additional); + desc.setRemoteId(artifactId); + desc.setTitle(filename); + desc.setAutomatedDownload(download); + desc.setRemoteAddress(remoteUrl); + + return new ArtifactTemplate(desc); + } + + /** + * Build template from ids contract. + * + * @param contract The ids contract offer. + * @return The contract template. + * @throws IllegalArgumentException if the passed contract is null. + */ + public static ContractTemplate fromIdsContract(final Contract contract) { + Utils.requireNonNull(contract, ErrorMessages.ENTITY_NULL); + + final var consumer = contract.getConsumer(); + final var date = contract.getContractDate(); + final var end = contract.getContractEnd(); + final var contractId = contract.getId(); + final var provider = contract.getProvider(); + final var start = contract.getContractStart(); + + // Add additional properties to map. + final var additional = propertiesToAdditional(contract.getProperties()); + + if (date != null) { + additional.put("ids:contractDate", date.toXMLFormat()); + } + + final var desc = new ContractDesc(); + desc.setAdditional(additional); + desc.setConsumer(consumer); + desc.setProvider(provider); + desc.setRemoteId(contractId); + + try { + desc.setEnd(getDateOf(end.toXMLFormat())); + } catch (DateTimeParseException ignored) { + // Default values don't need to be set here. + } + + try { + desc.setStart(getDateOf(start.toXMLFormat())); + } catch (DateTimeParseException ignored) { + // Default values don't need to be set here. + } + + return new ContractTemplate(null, desc, null); + } + + /** + * Build template from ids rule. + * + * @param rule The ids rule. + * @return The rule template. + * @throws IllegalArgumentException if the rule is null. + * @throws io.dataspaceconnector.exceptions.RdfBuilderException + * if the rule cannot be converted to string. + */ + public static RuleTemplate fromIdsRule(final Rule rule) { + Utils.requireNonNull(rule, ErrorMessages.ENTITY_NULL); + + final var value = IdsUtils.toRdf(rule); + final var desc = new ContractRuleDesc(); + desc.setRemoteId(rule.getId()); + desc.setValue(value); + + if (rule.getTitle() != null) { + desc.setTitle(rule.getTitle().toString()); + } + + return new RuleTemplate(desc); + } + + /** + * Map ids property map to additional map for the internal data model. + * If the argument is null an empty map will be returned. + * + * @param properties A string object map. + * @return A map containing all properties that could be extracted. + */ + private static Map propertiesToAdditional( + final Map properties) { + final Map additional = new ConcurrentHashMap<>(); + if (properties != null) { + for (final Map.Entry entry : properties.entrySet()) { + if (entry.getValue() != null) { + additional.put(entry.getKey(), entry.getValue().toString()); + } + } + } + + return additional; + } + + /** + * Convert a string to a {@link ZonedDateTime}. + * + * @param calendar The time as string. + * @return The new ZonedDateTime object. + * @throws DateTimeParseException if the string could not be converted. + */ + public static ZonedDateTime getDateOf(final String calendar) { + return ZonedDateTime.parse(calendar); + } + + /** + * Returns the first endpoint documentations of the first endpoint. + * + * @param endpoints The list of endpoints. + * @return The endpoint documentation. + */ + private static Optional getFirstEndpointDocumentation( + final List endpoints) { + Optional output = Optional.empty(); + + if (!endpoints.isEmpty()) { + final var first = endpoints.get(0); + + if (first.getEndpointDocumentation() != null + && !first.getEndpointDocumentation().isEmpty()) { + output = Optional.of(first.getEndpointDocumentation().get(0)); + } + } + + return output; + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/MessageUtils.java b/src/main/java/io/dataspaceconnector/utils/MessageUtils.java new file mode 100644 index 000000000..f26562cf1 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/MessageUtils.java @@ -0,0 +1,270 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.ArtifactRequestMessage; +import de.fraunhofer.iais.eis.DescriptionRequestMessage; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.RejectionMessage; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.ResourceUpdateMessage; +import io.dataspaceconnector.exceptions.MessageBuilderException; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.MessageRequestException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import de.fraunhofer.isst.ids.framework.communication.http.InfomodelMessageBuilder; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayload; +import lombok.extern.log4j.Log4j2; +import okhttp3.MultipartBody; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * Class providing util methods for message utility. + */ +@Log4j2 +public final class MessageUtils { + + /** + * Class constructor without params. + */ + private MessageUtils() { + // not used + } + + /** + * Extract requested element from ids description request message. + * + * @param message The ids message. + * @return The id of the requested element. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractRequestedElement(final DescriptionRequestMessage message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getRequestedElement(); + } + + /** + * Extract requested artifact from ids artifact request message. + * + * @param message The ids message. + * @return The id of the requested artifact. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractRequestedArtifact(final ArtifactRequestMessage message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getRequestedArtifact(); + } + + /** + * Extract transfer contract from ids artifact request message. + * + * @param message The ids message. + * @return The id of the transfer contract. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractTransferContract(final ArtifactRequestMessage message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getTransferContract(); + } + + /** + * Extract affected resource from ids resource update message. + * + * @param message The ids message. + * @return The id of the affected resource. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractAffectedResource(final ResourceUpdateMessage message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getAffectedResource(); + } + + /** + * Extract issuer connector from incoming ids message. + * + * @param message The ids message. + * @return The issuer connector of an ids message. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractIssuerConnector(final Message message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getIssuerConnector(); + } + + /** + * Extract the ids of an ids message. + * + * @param message The ids message. + * @return The ids of an ids message. + * @throws IllegalArgumentException If the message is null. + */ + public static URI extractMessageId(final Message message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getId(); + } + + /** + * Extract the ids of an ids message. + * + * @param message The ids message. + * @return The ids of an ids message. + * @throws IllegalArgumentException If the message is null. + */ + public static String extractModelVersion(final Message message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getModelVersion(); + } + + /** + * Extract the rejection reason from an ids rejection message. + * + * @param message The ids message. + * @return The rejection reason. + * @throws IllegalArgumentException If the message is null. + */ + public static RejectionReason extractRejectionReason(final RejectionMessage message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.getRejectionReason(); + } + + /** + * Check if the received message is empty. + * + * @param message The message. + * @throws MessageEmptyException If the message is empty. + */ + public static void checkForEmptyMessage(final Message message) throws MessageEmptyException { + if (message == null) { + throw new MessageEmptyException("The incoming request message cannot be null."); + } + } + + /** + * Check if the outbound model version of the requesting connector is listed in the inbound + * model versions. + * + * @param versionString The outbound model version of the requesting connector. + * @param inboundVersions The inbound model version of the current connector. + * @throws VersionNotSupportedException If the Information Model version is not supported. + */ + public static void checkForVersionSupport(final String versionString, + final List inboundVersions) + throws VersionNotSupportedException { + boolean versionSupported = false; + for (final var version : inboundVersions) { + if (version.equals(versionString)) { + versionSupported = true; + break; + } + } + + if (!versionSupported) { + throw new VersionNotSupportedException("Information Model version not supported."); + } + } + + /** + * Build http multipart message with a payload and an ids message as header. + * + * @param header The ids message. + * @param payload The message's payload. + * @return A multipart body or error. + * @throws MessageBuilderException If the message could not be built. + */ + public static MultipartBody buildIdsMultipartMessage(final Message header, final Object payload) + throws MessageBuilderException { + try { + return InfomodelMessageBuilder.messageWithString(header, String.valueOf(payload)); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Message could not be built. [exception=({})]", e.getMessage(), e); + } + throw new MessageBuilderException("Message could not be built.", e); + } + } + + /** + * Extract the header part from the ids framework response. + * + * @param message The ids response message as map. + * @return The ids header. + * @throws IllegalArgumentException If the message is null. + */ + public static String extractHeaderFromMultipartMessage(final Map message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.get("header"); + } + + /** + * Extract the payload part from the ids framework response. + * + * @param message The ids response message as map. + * @return The ids payload. + * @throws IllegalArgumentException If the message is null. + */ + public static String extractPayloadFromMultipartMessage(final Map message) { + Utils.requireNonNull(message, ErrorMessages.MESSAGE_NULL); + return message.get("payload"); + } + + /** + * Read string from stream. + * + * @param payload The message payload as stream. + * @return The stream's content. + * @throws IllegalArgumentException if the payload is null. + * @throws IOException If the stream could not be read. + */ + public static String getStreamAsString(final MessagePayload payload) throws IOException { + Utils.requireNonNull(payload, ErrorMessages.MISSING_PAYLOAD); + Utils.requireNonNull(payload.getUnderlyingInputStream(), ErrorMessages.MISSING_PAYLOAD); + return IOUtils.toString(payload.getUnderlyingInputStream(), StandardCharsets.UTF_8); + } + + /** + * Get the payload as string. + * + * @param payload The message's payload. + * @return The payload as string. + * @throws MessageRequestException If the payload could not be processed. + */ + public static String getPayloadAsString(final MessagePayload payload) + throws MessageRequestException { + if (payload == null) { + throw new MessageRequestException(ErrorMessages.MISSING_PAYLOAD.toString()); + } + + String content; + try { + content = MessageUtils.getStreamAsString(payload); + } catch (IOException e) { + throw new MessageRequestException(ErrorMessages.MALFORMED_PAYLOAD.toString(), e); + } + + // If request is empty, return rejection message. + if (content.equals("")) { + throw new MessageRequestException(ErrorMessages.MISSING_PAYLOAD.toString()); + } + + return content; + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/MetadataUtils.java b/src/main/java/io/dataspaceconnector/utils/MetadataUtils.java new file mode 100644 index 000000000..77682625d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/MetadataUtils.java @@ -0,0 +1,167 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Contains utility methods for updating entity attributes. + */ +public final class MetadataUtils { + + /** + * Default constructor. + */ + private MetadataUtils() { + // not used + } + + /** + * Update string. + * + * @param oldTitle Old string. + * @param newTitle New string. + * @param defaultTitle Default value. + * @return Optional with the new value or without a value. + */ + public static Optional updateString(final String oldTitle, + final String newTitle, + final String defaultTitle) { + final var newValue = newTitle == null ? defaultTitle : newTitle; + if (oldTitle == null || !oldTitle.equals(newValue)) { + return Optional.of(newValue); + } + + return Optional.empty(); + } + + /** + * Update uri. + * + * @param oldUri Old uri. + * @param newUri New uri. + * @param defaultUri Default value. + * @return Optional with the new value or without a value. + */ + public static Optional updateUri(final URI oldUri, final URI newUri, + final URI defaultUri) { + final var newValue = newUri == null ? defaultUri : newUri; + if (oldUri == null || !oldUri.equals(newValue)) { + return Optional.of(newValue); + } + + return Optional.empty(); + } + + /** + * Update date. + * + * @param oldDate Old date. + * @param newDate New date. + * @param defaultDate Default value. + * @return Optional with the new value or without a value. + */ + public static Optional updateDate(final ZonedDateTime oldDate, + final ZonedDateTime newDate, + final ZonedDateTime defaultDate) { + final var newValue = newDate == null ? defaultDate : newDate; + if (oldDate == null || !oldDate.equals(newValue)) { + return Optional.of(newValue); + } + + return Optional.empty(); + } + + /** + * Update list of strings. + * + * @param oldList Old list. + * @param newList New list. + * @param defaultList Default values. + * @return Optional with the new value or without a value. + */ + public static Optional> updateStringList( + final List oldList, + final List newList, + final List defaultList) { + final var newValues = cleanStringList(newList == null ? defaultList : newList); + + if (oldList == null || !oldList.equals(newValues)) { + return Optional.of(newValues); + } + + return Optional.empty(); + } + + /** + * Update map of strings. + * + * @param oldMap Old map. + * @param newMap New map. + * @param defaultMap Default values. + * @return Optional with the new value or without a value. + */ + public static Optional> updateStringMap( + final Map oldMap, final Map newMap, + final Map defaultMap) { + // TODO Implement cleaning like in updateStringList + final var newValues = newMap == null ? defaultMap : newMap; + if (oldMap == null || !oldMap.equals(newValues)) { + return Optional.of(newValues); + } + + return Optional.empty(); + } + + /** + * Clean list of strings. + * + * @param list List of strings. + * @return Cleared list. + */ + public static List cleanStringList(final List list) { + var result = removeNullFromList(list); + result = removeEmptyStringFromList(result); + return result; + } + + /** + * Remove null values from list. + * + * @param list List of values. + * @param Class type. + * @return List without null values. + */ + public static List removeNullFromList(final List list) { + return list.stream().filter(Objects::nonNull).collect(Collectors.toList()); + } + + /** + * Remove empty values from list of strings. + * + * @param list List of strings. + * @return List without empty strings. + */ + public static List removeEmptyStringFromList(final List list) { + return list.stream().filter(x -> !x.isEmpty()).collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/PatternUtils.java b/src/main/java/io/dataspaceconnector/utils/PatternUtils.java new file mode 100644 index 000000000..d0d1291c4 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/PatternUtils.java @@ -0,0 +1,230 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.BinaryOperator; +import de.fraunhofer.iais.eis.ConstraintBuilder; +import de.fraunhofer.iais.eis.DutyBuilder; +import de.fraunhofer.iais.eis.LeftOperand; +import de.fraunhofer.iais.eis.PermissionBuilder; +import de.fraunhofer.iais.eis.ProhibitionBuilder; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.RdfResource; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; + +import java.net.URI; + +/** + * Contains utility methods for creating example ids rules. + */ +public final class PatternUtils { + + /** + * Default constructor. + */ + private PatternUtils() { + // not used + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildProvideAccessRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Allow Data Usage"))) + ._description_(Util.asList(new TypedLiteral("provide-access"))) + ._action_(Util.asList(Action.USE)) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildProhibitAccessRule() { + return new ProhibitionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("prohibit-access"))) + ._action_(Util.asList(Action.USE)) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildNTimesUsageRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("n-times-usage"))) + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.LTEQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:double"))) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildDurationUsageRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("duration-usage"))) + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource("PT1M30.5S", URI.create("xsd:duration"))) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildIntervalUsageRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-during-interval"))) + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", + URI.create("xsd:dateTimeStamp"))) + .build(), new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.BEFORE) + ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", + URI.create("xsd:dateTimeStamp"))) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildUsageUntilDeletionRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-until-deletion"))) + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", + URI.create("xsd:dateTimeStamp"))) + .build(), new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.BEFORE) + ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", + URI.create("xsd:dateTimeStamp"))) + .build())) + ._postDuty_(Util.asList(new DutyBuilder() + ._action_(Util.asList(Action.DELETE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.TEMPORAL_EQUALS) + ._rightOperand_(new RdfResource("2020-07-11T00:00:00Z", + URI.create("xsd:dateTimeStamp"))) + .build())) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildUsageLoggingRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-logging"))) + ._action_(Util.asList(Action.USE)) + ._postDuty_(Util.asList(new DutyBuilder() + ._action_(Util.asList(Action.LOG)) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildUsageNotificationRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-notification"))) + ._action_(Util.asList(Action.USE)) + ._postDuty_(Util.asList(new DutyBuilder() + ._action_(Util.asList(Action.NOTIFY)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.ENDPOINT) + ._operator_(BinaryOperator.DEFINES_AS) + ._rightOperand_(new RdfResource( + "https://localhost:8080/api/ids" + + "/data", URI.create("xsd:anyURI"))) + .build())) + .build())) + .build(); + } + + /** + * Build ids rule. + * + * @return The ids rule. + * @throws de.fraunhofer.iais.eis.util.ConstraintViolationException if rule creation fails. + */ + public static Rule buildConnectorRestrictedUsageRule() { + return new PermissionBuilder() + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("connector-restriction"))) + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(new ConstraintBuilder() + ._leftOperand_(LeftOperand.SYSTEM) + ._operator_(BinaryOperator.SAME_AS) + ._rightOperand_( + new RdfResource("https://example.com", + URI.create("xsd:anyURI"))) + .build())) + .build(); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/RuleUtils.java b/src/main/java/io/dataspaceconnector/utils/RuleUtils.java new file mode 100644 index 000000000..63c94f412 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/RuleUtils.java @@ -0,0 +1,462 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.AbstractConstraint; +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.BinaryOperator; +import de.fraunhofer.iais.eis.ConstraintImpl; +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.Duty; +import de.fraunhofer.iais.eis.DutyImpl; +import de.fraunhofer.iais.eis.LeftOperand; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.PermissionImpl; +import de.fraunhofer.iais.eis.Prohibition; +import de.fraunhofer.iais.eis.Rule; +import io.dataspaceconnector.exceptions.ContractException; +import io.dataspaceconnector.exceptions.InvalidInputException; +import io.dataspaceconnector.model.TimeInterval; +import io.dataspaceconnector.services.usagecontrol.PolicyPattern; +import lombok.extern.log4j.Log4j2; + +import java.net.URI; +import java.text.ParseException; +import java.time.Duration; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; + +/** + * Contains utility methods for validating the content of ids rules. + */ +@Log4j2 +public final class RuleUtils { + + /** + * Constructor without params. + */ + private RuleUtils() { + // not used + } + + /** + * Read the properties of an ids rule to automatically recognize the policy pattern. + * + * @param rule The ids rule. + * @return The recognized policy pattern. + */ + public static PolicyPattern getPatternByRule(final Rule rule) { + PolicyPattern detectedPattern = null; + + if (rule instanceof Prohibition) { + detectedPattern = PolicyPattern.PROHIBIT_ACCESS; + } else if (rule instanceof Permission) { + final var constraints = rule.getConstraint(); + final var postDuties = ((Permission) rule).getPostDuty(); + + if (constraints != null && constraints.get(0) != null) { + if (constraints.size() > 1) { + if (postDuties != null && postDuties.get(0) != null) { + detectedPattern = PolicyPattern.USAGE_UNTIL_DELETION; + } else { + detectedPattern = PolicyPattern.USAGE_DURING_INTERVAL; + } + } else { + final var firstConstraint = (ConstraintImpl) constraints.get(0); + final var leftOperand = firstConstraint.getLeftOperand(); + final var operator = firstConstraint.getOperator(); + if (leftOperand == LeftOperand.COUNT) { + detectedPattern = PolicyPattern.N_TIMES_USAGE; + } else if (leftOperand == LeftOperand.ELAPSED_TIME) { + detectedPattern = PolicyPattern.DURATION_USAGE; + } else if (leftOperand == LeftOperand.SYSTEM + && operator == BinaryOperator.SAME_AS) { + detectedPattern = PolicyPattern.CONNECTOR_RESTRICTED_USAGE; + } else { + detectedPattern = null; + } + } + } else { + if (postDuties != null && postDuties.get(0) != null) { + final var action = postDuties.get(0).getAction().get(0); + if (action == Action.NOTIFY) { + detectedPattern = PolicyPattern.USAGE_NOTIFICATION; + } else if (action == Action.LOG) { + detectedPattern = PolicyPattern.USAGE_LOGGING; + } else { + detectedPattern = null; + } + } else { + detectedPattern = PolicyPattern.PROVIDE_ACCESS; + } + } + } + + return detectedPattern; + } + + /** + * Check rule for post duties. + * + * @param rule The ids rule. + * @return True if resource should be deleted, false if not. + * @throws DateTimeParseException If the policy could not be checked. + */ + public static boolean checkRuleForPostDuties(final Rule rule) throws DateTimeParseException { + if (rule instanceof PermissionImpl || rule instanceof DutyImpl) { + final var postDuties = ((Permission) rule).getPostDuty(); + if (postDuties != null && !postDuties.isEmpty()) { + return checkDutiesForDeletion(postDuties); + } + } + + return false; + } + + /** + * Check duties for deletion. + * + * @param duties The post duty list. + * @return True if resource should be deleted, false if not. + * @throws DateTimeParseException If the policy could not be checked. + */ + public static boolean checkDutiesForDeletion(final ArrayList duties) + throws DateTimeParseException { + for (final var duty : duties) { + for (final var action : duty.getAction()) { + if (action == Action.DELETE) { + return checkRuleForDeletion(duty); + } + } + } + return false; + } + + /** + * Checks if the specified duration since resource creation or the specified maximum date for + * resource access has already been exceeded. + * + * @param rule a {@link de.fraunhofer.iais.eis.Rule} object. + * @return true, if the duration or date has been exceeded; false otherwise. + * @throws DateTimeParseException if a duration cannot be parsed. + */ + public static boolean checkRuleForDeletion(final Rule rule) throws DateTimeParseException { + final var max = getDate(rule); + if (max != null) { + return checkDate(ZonedDateTime.now(ZoneOffset.UTC), max); + } else { + return false; + } + } + + /** + * Checks whether the current date is later than the specified one. + * + * @param dateNow the current date. + * @param maxAccess the target date. + * @return true, if the current date is later than the target date; false otherwise. + */ + public static boolean checkDate(final ZonedDateTime dateNow, final ZonedDateTime maxAccess) { + return !dateNow.isAfter(maxAccess); + } + + /** + * Gets the endpoint value to send notifications to defined in a policy. + * + * @param rule The ids rule. + * @return The endpoint value. + */ + public static String getEndpoint(final Rule rule) throws NullPointerException { + final var constraint = rule.getConstraint().get(0); + return ((ConstraintImpl) constraint).getRightOperand().getValue(); + } + + /** + * Gets the allowed number of accesses defined in a policy. + * + * @param rule the policy rule object. + * @return the number of allowed accesses. + */ + public static Integer getMaxAccess(final Rule rule) throws NumberFormatException { + final var constraint = rule.getConstraint().get(0); + final var value = ((ConstraintImpl) constraint).getRightOperand().getValue(); + final var operator = ((ConstraintImpl) constraint).getOperator(); + + int number; + try { + number = Integer.parseInt(value); + } catch (NumberFormatException e) { + if (log.isDebugEnabled()) { + log.debug("Failed to parse value to integer. [exception=({})]", + e.getMessage(), e); + } + throw e; + } + + if (number < 0) { + number = 0; + } + + switch (operator) { + case EQ: + case LTEQ: + return number; + case LT: + return number - 1; + default: + return 0; + } + } + + /** + * Gets the time interval defined in a policy. + * + * @param rule the policy rule object. + * @return the time interval. + */ + public static TimeInterval getTimeInterval(final Rule rule) throws ParseException { + final var interval = new TimeInterval(); + + for (var constraint : rule.getConstraint()) { + final var operator = ((ConstraintImpl) constraint).getOperator(); + if (operator == BinaryOperator.AFTER) { + final var value = ((ConstraintImpl) constraint).getRightOperand().getValue(); + final var start = MappingUtils.getDateOf(value); + interval.setStart(start); + } else if (operator == BinaryOperator.BEFORE) { + final var value = ((ConstraintImpl) constraint).getRightOperand().getValue(); + final var end = MappingUtils.getDateOf(value); + interval.setEnd(end); + } + } + return interval; + } + + /** + * Gets the PIP endpoint path value defined in a policy. + * + * @param rule the policy rule object. + * @return the pip endpoint value. + */ + public static URI getPipEndpoint(final Rule rule) { + final var constraint = rule.getConstraint().get(0); + return ((ConstraintImpl) constraint).getPipEndpoint(); + } + + /** + * Gets the date value defined in a policy. + * + * @param rule the policy constraint object. + * @return the date or null. + */ + public static ZonedDateTime getDate(final Rule rule) throws DateTimeParseException { + final var constraint = rule.getConstraint().get(0); + final var date = ((ConstraintImpl) constraint).getRightOperand().getValue(); + + return MappingUtils.getDateOf(date); + } + + /** + * Gets the duration value defined in a policy. + * + * @param rule The ids rule. + * @return The duration or null. + * @throws DateTimeParseException If the duration cannot be parsed. + */ + public static java.time.Duration getDuration(final Rule rule) + throws DateTimeParseException { + final var constraint = rule.getConstraint().get(0); + final var type = ((ConstraintImpl) constraint).getRightOperand().getType(); + + if (type.equals("xsd:duration")) { + final var duration = ((ConstraintImpl) constraint).getRightOperand().getValue(); + return java.time.Duration.parse(duration); + } else { + return null; + } + } + + /** + * Add duration to a date to calculate a new date. + * + * @param original The previous date. + * @param duration The duration to add. + * @return The new date. + */ + public static ZonedDateTime getCalculatedDate( + final ZonedDateTime original, final Duration duration) { + return original.plus(duration); + } + + /** + * Check if each rule contains a target. + * + * @param ruleList The ids rule list. + * @throws InvalidInputException If a target is missing. + */ + public static void validateRuleTarget(final List ruleList) + throws InvalidInputException { + for (final var rule : ruleList) { + final var target = rule.getTarget(); + if (target == null || target.toString().equals("")) { + throw new InvalidInputException(ErrorMessages.MISSING_TARGET.toString()); + } + } + } + + /** + * Compare two lists of rules with each other. + * + * @param oldContract The old contract. + * @param newContract The new contract. + * @throws ContractException If a mismatch has been detected. + */ + public static void validateRuleContent(final Contract oldContract, + final Contract newContract) throws ContractException { + if (oldContract == null || newContract == null) { + throw new ContractException(ErrorMessages.EMPTY_CONTRACT.toString()); + } + + if (!comparePermissions(oldContract.getPermission(), newContract.getPermission())) { + throw new ContractException("Different permissions"); + } + + if (!compareProhibitions(oldContract.getProhibition(), newContract.getProhibition())) { + throw new ContractException("Different prohibitions."); + } + + if (!compareObligations(oldContract.getObligation(), newContract.getObligation())) { + throw new ContractException("Different obligations."); + } + } + + /** + * Compare two permission lists to each other. + * + * @param lList One list. + * @param rList The other list. + * @return True, if the lists are equal, false if not. + */ + public static boolean comparePermissions(final ArrayList lList, + final ArrayList rList) { + return compareDuties(lList, rList) && compareRules(lList, rList); + } + + /** + * Compare two prohibition lists to each other. + * + * @param lList One list. + * @param rList The other list. + * @return True, if the lists are equal, false if not. + */ + public static boolean compareProhibitions(final ArrayList lList, + final ArrayList rList) { + return compareRules(lList, rList); + } + + /** + * Compare two obligation lists to each other. + * + * @param lList One list. + * @param rList The other list. + * @return True, if the lists are equal, false if not. + */ + public static boolean compareObligations(final ArrayList lList, + final ArrayList rList) { + return compareRules(lList, rList); + } + + /** + * Compare the content of two permissions lists. + * + * @param lList List of rules from original contract. + * @param rList List of rules from the contract that should be compared. + * @return true if both rules are the same. + */ + private static boolean compareDuties(final ArrayList lList, + final ArrayList rList) { + return Utils.compareList(lList, rList, RuleUtils::compareDuties); + } + + /** + * Compare the content of two rule lists. + * + * @param oldRules List of rules from original contract. + * @param newRules List of rules from the contract that should be compared. + * @return true if both rules are the same. + */ + public static boolean compareRules(final ArrayList oldRules, + final ArrayList newRules) { + return Utils.compareList(oldRules, newRules, RuleUtils::compareRule); + } + + /** + * Compares the content of two constraint lists. + * + * @param lList List of rules from original contract. + * @param rList List of rules from the contract that should be compared. + * @return true if both rules are the same. + */ + private static boolean compareConstraints( + final ArrayList lList, + final ArrayList rList) { + return Utils.compareList(lList, rList, RuleUtils::compareConstraint); + } + + /** + * Compares the content of two actions lists. + * + * @param lList List of rules from original contract. + * @param rList List of rules from the contract that should be compared. + * @return true if the actions are the same. + */ + private static boolean compareActions(final ArrayList lList, + final ArrayList rList) { + return Utils.compareList(lList, rList, RuleUtils::compareAction); + } + + private static boolean compareDuties(final T lObj, final T rObj) { + return compareRules(lObj.getPreDuty(), rObj.getPreDuty()) + && compareRules(lObj.getPostDuty(), rObj.getPostDuty()); + } + + private static boolean compareRule(final T lObj, final T rObj) { + return compareActions(lObj.getAction(), rObj.getAction()) + && compareConstraints(lObj.getConstraint(), rObj.getConstraint()); + } + + private static boolean compareConstraint(final T lObj, + final T rObj) { + return lObj.toRdf().equals(rObj.toRdf()); + } + + private static boolean compareAction(final T lObj, final T rObj) { + return lObj.equals(rObj); + } + + /** + * Get current system date. + * + * @return The date object. + */ + public static ZonedDateTime getCurrentDate() { + return ZonedDateTime.now(ZoneOffset.UTC); + } + +} diff --git a/src/main/java/io/dataspaceconnector/utils/SelfLinkHelper.java b/src/main/java/io/dataspaceconnector/utils/SelfLinkHelper.java new file mode 100644 index 000000000..4f7bac120 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/SelfLinkHelper.java @@ -0,0 +1,234 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.view.AgreementViewAssembler; +import io.dataspaceconnector.view.ArtifactViewAssembler; +import io.dataspaceconnector.view.CatalogViewAssembler; +import io.dataspaceconnector.view.ContractRuleViewAssembler; +import io.dataspaceconnector.view.ContractViewAssembler; +import io.dataspaceconnector.view.OfferedResourceViewAssembler; +import io.dataspaceconnector.view.RepresentationViewAssembler; +import io.dataspaceconnector.view.RequestedResourceViewAssembler; +import io.dataspaceconnector.view.SelfLinking; + +import java.net.URI; + +/** + * This is a helper class for retrieving self-links of a database entity. + */ +public final class SelfLinkHelper { + + /** + * View assembler for catalogs. + */ + static final CatalogViewAssembler CATALOG_ASSEMBLER = new CatalogViewAssembler(); + + /** + * View assembler for offered resources. + */ + static final OfferedResourceViewAssembler OFFERED_RESOURCE_ASSEMBLER + = new OfferedResourceViewAssembler(); + + /** + * View assembler for requested resources. + */ + static final RequestedResourceViewAssembler REQUESTED_RESOURCE_ASSEMBLER + = new RequestedResourceViewAssembler(); + + /** + * View assembler for representations. + */ + static final RepresentationViewAssembler REPRESENTATION_ASSEMBLER + = new RepresentationViewAssembler(); + + /** + * View assembler for artifacts. + */ + static final ArtifactViewAssembler ARTIFACT_ASSEMBLER = new ArtifactViewAssembler(); + + /** + * View assembler for contracts. + */ + static final ContractViewAssembler CONTRACT_ASSEMBLER = new ContractViewAssembler(); + + /** + * View assembler for contract rules. + */ + static final ContractRuleViewAssembler RULE_ASSEMBLER = new ContractRuleViewAssembler(); + + /** + * View assembler for contract agreements. + */ + static final AgreementViewAssembler AGREEMENT_ASSEMBLER = new AgreementViewAssembler(); + + /** + * Default constructor. + */ + private SelfLinkHelper() { + // not used + } + + /** + * This function is a helper function for hiding the problem that the self-link is always + * received through the concrete assembler. + * + * @param entity The entity. + * @param Generic type of database entity. + * @return The abstract entity. + */ + public static URI getSelfLink(final T entity) { + if (entity instanceof Catalog) { + return getSelfLink((Catalog) entity); + } else if (entity instanceof OfferedResource) { + return getSelfLink((OfferedResource) entity); + } else if (entity instanceof RequestedResource) { + return getSelfLink((RequestedResource) entity); + } else if (entity instanceof Representation) { + return getSelfLink((Representation) entity); + } else if (entity instanceof Artifact) { + return getSelfLink((Artifact) entity); + } else if (entity instanceof Contract) { + return getSelfLink((Contract) entity); + } else if (entity instanceof ContractRule) { + return getSelfLink((ContractRule) entity); + } else if (entity instanceof Agreement) { + return getSelfLink((Agreement) entity); + } + + throw new UnreachableLineException(ErrorMessages.UNKNOWN_TYPE); + } + + /** + * Get self-link from abstract entity. + * + * @param entity The entity. + * @param describer The entity view assembler. + * @param The type of the entity. + * @param The type of the assembler. + * @return The abstract entity and its self-link. + * @throws ResourceNotFoundException If the entity could not be found. + */ + public static URI getSelfLink( + final T entity, final S describer) throws ResourceNotFoundException { + try { + return describer.getSelfLink(entity.getId()).toUri(); + } catch (IllegalStateException exception) { + throw new ResourceNotFoundException(ErrorMessages.EMTPY_ENTITY.toString(), exception); + } + } + + /** + * Get self-link of catalog. + * + * @param catalog The catalog. + * @return The self-link of the catalog. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final Catalog catalog) throws ResourceNotFoundException { + return getSelfLink(catalog, CATALOG_ASSEMBLER); + } + + /** + * Get self-link of offered resource. + * + * @param resource The offered resource. + * @return The self-link of the offered resource. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final OfferedResource resource) throws ResourceNotFoundException { + return getSelfLink(resource, OFFERED_RESOURCE_ASSEMBLER); + } + + /** + * Get self-link of requested resource. + * + * @param resource The requested resource. + * @return The self-link of the requested resource. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final RequestedResource resource) + throws ResourceNotFoundException { + return getSelfLink(resource, REQUESTED_RESOURCE_ASSEMBLER); + } + + /** + * Get self-link of representation. + * + * @param representation The representation. + * @return The self-link of the representation. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final Representation representation) + throws ResourceNotFoundException { + return getSelfLink(representation, REPRESENTATION_ASSEMBLER); + } + + /** + * Get self-link of artifact. + * + * @param artifact The artifact. + * @return The self-link of the artifact. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final Artifact artifact) throws ResourceNotFoundException { + return getSelfLink(artifact, ARTIFACT_ASSEMBLER); + } + + /** + * Get self-link of contract. + * + * @param contract The contract. + * @return The self-link of the contract. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final Contract contract) throws ResourceNotFoundException { + return getSelfLink(contract, CONTRACT_ASSEMBLER); + } + + /** + * Get self-link of rule. + * + * @param rule The rule. + * @return The self-link of the rule. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final ContractRule rule) throws ResourceNotFoundException { + return getSelfLink(rule, RULE_ASSEMBLER); + } + + /** + * Get self-link of agreement. + * + * @param agreement The agreement. + * @return The self-link of the agreement. + * @throws ResourceNotFoundException If the resource could not be loaded. + */ + public static URI getSelfLink(final Agreement agreement) throws ResourceNotFoundException { + return getSelfLink(agreement, AGREEMENT_ASSEMBLER); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/TemplateUtils.java b/src/main/java/io/dataspaceconnector/utils/TemplateUtils.java new file mode 100644 index 000000000..36027189e --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/TemplateUtils.java @@ -0,0 +1,176 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.Resource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.templates.ArtifactTemplate; +import io.dataspaceconnector.model.templates.ContractTemplate; +import io.dataspaceconnector.model.templates.RepresentationTemplate; +import io.dataspaceconnector.model.templates.ResourceTemplate; +import io.dataspaceconnector.model.templates.RuleTemplate; +import lombok.extern.log4j.Log4j2; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides methods for building entity templates. + */ +@Log4j2 +public final class TemplateUtils { + + /** + * Default constructor. + */ + private TemplateUtils() { + // not used + } + + /** + * Build resource template from ids resource. + * + * @param resource The ids resource. + * @return The resource template. + */ + public static ResourceTemplate getResourceTemplate( + final Resource resource) { + return MappingUtils.fromIdsResource(resource); + } + + /** + * Build a list of representation templates from ids resource. + * + * @param resource The ids resource. + * @param artifacts List of requested artifacts (remote id). + * @param download Indicated whether the artifact will be downloaded automatically. + * @param accessUrl The access url to fetch the data. + * @return List of representation templates. + */ + public static List getRepresentationTemplates(final Resource resource, + final List artifacts, + final boolean download, + final URI accessUrl) { + final var list = new ArrayList(); + + // Iterate over all representations. + final var representationList = resource.getRepresentation(); + try { + for (final var representation : Utils.requireNonNull(representationList, + ErrorMessages.LIST_NULL)) { + final var template = MappingUtils.fromIdsRepresentation(representation); + final var artifactTemplates = getArtifactTemplates(representation, + artifacts, download, accessUrl); + + // Representation is only saved if it contains requested artifacts. + if (!artifactTemplates.isEmpty()) { + template.setArtifacts(artifactTemplates); + list.add(template); + } + } + } catch (IllegalArgumentException exception) { + if (log.isDebugEnabled()) { + log.debug("Resource does not contain any representations. [resourceId=({})]", + resource.getId()); + } + } + + return list; + } + + /** + * Build a list of artifact templates from ids representation. + * + * @param representation The ids representation. + * @param requestedArtifacts List of requested artifacts (remote ids). + * @param download Indicated whether the artifact will be downloaded automatically. + * @param remoteUrl The provider's url for receiving artifact request messages. + * @return List of artifact templates. + */ + public static List getArtifactTemplates(final Representation representation, + final List requestedArtifacts, + final boolean download, + final URI remoteUrl) { + final var list = new ArrayList(); + + // Iterate over all artifacts. + final var artifactList = representation.getInstance(); + + try { + for (final var artifact : Utils.requireNonNull(artifactList, ErrorMessages.LIST_NULL)) { + final var id = artifact.getId(); + + // Artifact is only saved if it has been requested. + if (requestedArtifacts.contains(id)) { + final var template = MappingUtils.fromIdsArtifact((Artifact) artifact, + download, remoteUrl); + list.add(template); + } + } + } catch (IllegalArgumentException exception) { + if (log.isDebugEnabled()) { + log.debug("Representation does not contain any artifacts. [representationId=({})]", + representation.getId()); + } + } + + return list; + } + + /** + * Build a list of contract templates from ids resource. + * NOTE: Keep method for later usage. + * + * @param resource The ids resource. + * @return List of contract templates. + */ + private static List getContractTemplates(final Resource resource) { + final var list = new ArrayList(); + + // Iterate over all contract offers. + final var contractList = resource.getContractOffer(); + for (final var contract : contractList) { + final var contractTemplate = MappingUtils.fromIdsContract(contract); + + contractTemplate.setRules(getRuleTemplates(contract)); + list.add(contractTemplate); + } + + return list; + } + + /** + * Build a list of rule templates from ids contract. + * + * @param contract The ids contract. + * @return List of rule templates. + */ + private static List getRuleTemplates(final Contract contract) { + final var list = new ArrayList(); + final var rules = ContractUtils.extractRulesFromContract(contract); + + for (final var rule : rules) { + final var template = MappingUtils.fromIdsRule(rule); + list.add(template); + } + + return list; + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/UUIDUtils.java b/src/main/java/io/dataspaceconnector/utils/UUIDUtils.java new file mode 100644 index 000000000..c26ae0dbb --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/UUIDUtils.java @@ -0,0 +1,152 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import io.dataspaceconnector.exceptions.UUIDCreationException; +import io.dataspaceconnector.exceptions.UUIDFormatException; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.regex.Pattern; + +/** + * This class offers support functions for working with UUIDs. + */ +public final class UUIDUtils { + + /** + * Default constructor. + */ + private UUIDUtils() { + // not used + } + + /** + * Finds all UUIDs in a string. + * + * @param input a string which maybe contains UUIDs. + * @return the list of found UUIDs. + */ + public static List findUuids(final String input) { + final var pairRegex = Pattern + .compile("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit" + + "}{12}"); + final var matcher = pairRegex.matcher(input); + + // Extract all UUIDs + var output = new ArrayList(); + while (matcher.find()) { + output.add(matcher.group(0)); + } + + return output; + } + + /** + * Extracts a UUID from a URI. If more than one UUID is found the last UUID is returned. See + * also {@link #uuidFromUri}. + * + * @param uri The URI from which the UUID should be extracted. + * @return the extracted UUID. + * @throws UUIDFormatException if the URI does not contain a parsable UUID. + */ + public static UUID uuidFromUri(final URI uri) throws UUIDFormatException { + try { + return uuidFromUri(uri, -1); + } catch (IndexOutOfBoundsException exception) { + // Convert the exception to an expected format + throw new UUIDFormatException("No uuid could be found in the uri.", exception); + } + } + + /** + * Extracts a UUID from a URI at a given position. + * + * @param uri the URI from which the UUID should be extracted. + * @param index the index when more then one UUID is found. Set to a negative number when the + * last UUID should be extracted. + * @return the extracted uuid. + * @throws UUIDFormatException if the URI does not contain a parsable UUID. + * @throws IndexOutOfBoundsException if no UUID can be found at the given index. + */ + public static UUID uuidFromUri(final URI uri, final int index) throws UUIDFormatException, + IndexOutOfBoundsException { + // Find all uuids in the uri + final var uuids = findUuids(uri.toString()); + + // Get only the uuid needed + final var stringUuid = uuids.get(index < 0 ? uuids.size() - 1 : index); + + // Convert the string to uuid element + try { + return UUID.fromString(stringUuid); + } catch (IllegalArgumentException exception) { + // This exception should never be thrown since the pattern matcher (in splitUuids) + // found the uuid. + throw new UUIDFormatException("Could not convert string to uuid. This indicates a " + + "problem with the uuid pattern.", exception); + } + } + + /** + * Generates a unique UUID, if it does not already exist. + * + * @param doesUuidExistFunc a function checking if a given UUID already exists + * @return generated UUID + * @throws UUIDCreationException if no unique UUID could be generated + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static UUID createUUID(final Function doesUuidExistFunc) + throws UUIDCreationException { + final var maxNumTries = 32; + return createUUID(doesUuidExistFunc, maxNumTries); + } + + /** + * Tries to generate a unique UUID, if it does not already exist, in a given number of tries. + * + * @param doesUuidExistFunc a function checking if a given UUID already exists + * @param maxNumTries a maximum number of retries for generating the UUID + * @return generated UUID + * @throws UUIDCreationException if no unique UUID could be generated + */ + public static UUID createUUID(final Function doesUuidExistFunc, + final long maxNumTries) + throws IllegalArgumentException, UUIDCreationException { + if (maxNumTries == 0) { + throw new IllegalArgumentException("The maximum number of tries must be at least 1."); + } + + long numTries = 0; + while (numTries < maxNumTries) { + final var uuid = UUID.randomUUID(); + + // Check if the created uuid already exists + if (!doesUuidExistFunc.apply(uuid)) { + // It does not, the generated uuid is new + return uuid; + } + + numTries++; + } + + throw new UUIDCreationException("Could not create a new uuid. No unused uuid could be " + + "found."); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/Utils.java b/src/main/java/io/dataspaceconnector/utils/Utils.java new file mode 100644 index 000000000..003613853 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/Utils.java @@ -0,0 +1,208 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * This utility class contains general purpose functions. + */ +public final class Utils { + + /** + * Default constructor. + */ + private Utils() { + // This constructor is intentionally empty. Nothing to do here. + } + + /** + * Check if an object is not null. + * + * @param obj The object to check. + * @param message The error message transmitted when the object is null. + * @param The type of the passed object. + * @return The passed object. + * @throws IllegalArgumentException if the passed object is null. + */ + public static T requireNonNull(final T obj, final ErrorMessages message) { + if (obj == null) { + throw new IllegalArgumentException(message.toString()); + } + + return obj; + } + + /** + * Convert a collection that may be null safely to a stream. + * If the collection is null an empty stream will be produced. + * + * @param collection The collection. May be null. + * @param The type of the elements in the collection. + * @return The stream over the elements of the collection. + */ + public static Stream toStream(final Collection collection) { + return Optional.ofNullable(collection).stream().flatMap(Collection::stream); + } + + /** + * Get a page from a list. + * + * @param list The list the page should be constructed from. + * @param pageable The page information. + * @param The type of the list elements. + * @return The new page. + */ + public static Page toPage(final List list, final Pageable pageable) { + Utils.requireNonNull(list, ErrorMessages.LIST_NULL); + Utils.requireNonNull(pageable, ErrorMessages.PAGEABLE_NULL); + + if (pageable.equals(Pageable.unpaged())) { + // All elements should be returned. + return new PageImpl<>(list, pageable, list.size()); + } + + final var start = (int) pageable.getOffset(); + + if (start > pageable.getPageSize()) { + // There are no more list elements. + return new PageImpl<>(new ArrayList<>(), pageable, list.size()); + } + + final var end = Math.min(start + pageable.getPageSize(), list.size()); + return new PageImpl<>(list.subList(start, end), pageable, list.size()); + } + + /** + * Check if a parameter is empty. + * + * @param Type of input parameter. + * @param param The parameter. + * @return True if it is empty, false if not. + */ + public static boolean isEmptyOrNull(final T param) { + if (param == null) { + return true; + } else { + return param.toString().equals(""); + } + } + + /** + * Creates a page request based on page, size, and sort inputs. + * + * @param page The page index. + * @param size The page size. + * @return The page request. + */ + public static PageRequest toPageRequest(final Integer page, final Integer size) { + final int pageIndex = (page != null && page > 0) ? page : DEFAULT_FIRST_PAGE; + final int sizeValue = (size != null && size > 0) ? Math.min(size, MAX_PAGE_SIZE) + : DEFAULT_PAGE_SIZE; + + return PageRequest.of(pageIndex, sizeValue); + } + + /** + * Default page size. + */ + public static final int DEFAULT_PAGE_SIZE = 30; + + /** + * Max page size. + */ + public static final int MAX_PAGE_SIZE = 100; + + /** + * Default first page. + */ + public static final int DEFAULT_FIRST_PAGE = 0; + + /** + * Compare two lists to each other. + * + * @param lList One list. + * @param rList The other list. + * @param compare The function that should be used for comparison. + * @param Type of the list. + * @return True if lists are equal, false if not. + */ + public static boolean compareList(final List lList, + final List rList, + final BiFunction compare) { + var isSame = true; + + if (isOnlyOneNull(lList, rList)) { + isSame = false; + } else if (lList != null /* && rList != null*/) { + final var lSet = makeUnique(lList, compare); + final var rSet = makeUnique(rList, compare); + + if (lSet.size() == rSet.size()) { + for (final var lObj : lSet) { + var found = false; + for (final var rObj : rSet) { + if (compare.apply(lObj, rObj)) { + found = true; + break; + } + } + + if (!found) { + // At least one element is different + isSame = false; + break; + } + } + } else { + // Two unique sets with different length must have different elements + isSame = false; + } + } + + return isSame; + } + + private static List makeUnique(final List list, + final BiFunction compare) { + final var output = new ArrayList<>(list); + for (int x = 0; x < output.size(); x++) { + final var obj = output.get(x); + for (int y = x + 1; y < output.size(); y++) { + if (compare.apply(obj, output.get(y))) { + output.remove(y); + --y; + } + } + } + + return output; + } + + private static boolean isOnlyOneNull(final T obj1, final T obj2) { + return (obj1 == null && obj2 != null) || (obj1 != null && obj2 == null); + } +} diff --git a/src/main/java/io/dataspaceconnector/utils/ValidationUtils.java b/src/main/java/io/dataspaceconnector/utils/ValidationUtils.java new file mode 100644 index 000000000..b3bf4e419 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/ValidationUtils.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.util.Map; + +import io.dataspaceconnector.model.QueryInput; + +/** + * This class provides methods to validate values. + */ +public final class ValidationUtils { + + /** + * Utility class does not have to be instantiated. + */ + private ValidationUtils() { } + + /** + * Checks a given query input. If any of the keys or values in the headers or params maps are + * null, blank, or empty, an exception is thrown. + * + * @param queryInput the query input to validate. + * @throws IllegalArgumentException if any of the keys or values are null, blank, or empty. + */ + public static void validateQueryInput(final QueryInput queryInput) { + + // Validate headers from the query input, if they are present + if (queryInput != null && queryInput.getHeaders() != null) { + validateMapContent(queryInput.getHeaders()); + } + + // Validate query parameters from the query input, if they are present + if (queryInput != null && queryInput.getParams() != null) { + validateMapContent(queryInput.getParams()); + } + + // Validate path variables from the query input, if they are present + if (queryInput != null && queryInput.getPathVariables() != null) { + validateMapContent(queryInput.getPathVariables()); + } + } + + /** + * Iterates over all entries in a map and checks if key or value are null, empty or blank. + * + * @param map the map to validate + * @throws IllegalArgumentException if any key or value in the map is null, emtpy or blank + */ + private static void validateMapContent(final Map map) { + for (Map.Entry entry : map.entrySet()) { + + // Check if key of the map entry is null, empty or blank + if (entry.getKey() == null || entry.getKey().isBlank()) { + throw new IllegalArgumentException("Map key in query input should not be " + + "null, blank or empty (key:" + entry.getKey() + + ", value: " + entry.getValue() + ")."); + } + + // Check if value of the map entry is null, empty or blank + if (entry.getValue() == null || entry.getValue().isBlank()) { + throw new IllegalArgumentException("Map value in query input should not be " + + "null, blank or empty (key:" + entry.getKey() + + ", value: " + entry.getValue() + ")."); + } + } + } + +} diff --git a/src/main/java/io/dataspaceconnector/utils/package-info.java b/src/main/java/io/dataspaceconnector/utils/package-info.java new file mode 100644 index 000000000..6e106cd67 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/utils/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Utility functions used across the io.dataspaceconnector package. + */ +package io.dataspaceconnector.utils; diff --git a/src/main/java/io/dataspaceconnector/view/AgreementView.java b/src/main/java/io/dataspaceconnector/view/AgreementView.java new file mode 100644 index 000000000..ba27f0376 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/AgreementView.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of agreement information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "agreements", itemRelation = "agreement") +public class AgreementView extends RepresentationModel { + + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * Remote id. + */ + private URI remoteId; + + /** + * True, if the policy negotiation has been successfully finished. + */ + private boolean confirmed; + + /** + * An ids contract agreement as rdf string. + */ + private String value; +} diff --git a/src/main/java/io/dataspaceconnector/view/AgreementViewAssembler.java b/src/main/java/io/dataspaceconnector/view/AgreementViewAssembler.java new file mode 100644 index 000000000..ec4c98568 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/AgreementViewAssembler.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.AgreementController; +import io.dataspaceconnector.model.Agreement; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +/** + * Assembles the REST resource for an agreement. + */ +@Component +public class AgreementViewAssembler + implements RepresentationModelAssembler, SelfLinking { + @Override + public final AgreementView toModel(final Agreement agreement) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(agreement, AgreementView.class); + view.add(getSelfLink(agreement.getId())); + + final var artifactLink = WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.AgreementsToArtifacts.class) + .getResource(agreement.getId(), null, null)) + .withRel("artifacts"); + view.add(artifactLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, AgreementController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/ArtifactView.java b/src/main/java/io/dataspaceconnector/view/ArtifactView.java new file mode 100644 index 000000000..659edaaa9 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ArtifactView.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of artifact information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "artifacts", itemRelation = "artifact") +public class ArtifactView extends RepresentationModel { + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * Remote id. + */ + private URI remoteId; + + /** + * The title of the artifact. + */ + private String title; + + /** + * Number of data accesses. + */ + private Long numAccessed; + + /** + * The byte size of the artifact. + */ + private long byteSize; + + /** + * The CRC32C CheckSum of the artifact. + */ + private long checkSum; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/ArtifactViewAssembler.java b/src/main/java/io/dataspaceconnector/view/ArtifactViewAssembler.java new file mode 100644 index 000000000..b5f8647af --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ArtifactViewAssembler.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.ArtifactController; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.QueryInput; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +/** + * Assembles the REST resource for an artifact. + */ +@Component +@NoArgsConstructor +public class ArtifactViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the ArtifactView from an Artifact. + * + * @param artifact The artifact. + * @return The new view. + */ + @Override + public ArtifactView toModel(final Artifact artifact) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(artifact, ArtifactView.class); + view.add(getSelfLink(artifact.getId())); + + final var dataLink = linkTo(methodOn(ArtifactController.class) + .getData(artifact.getId(), new QueryInput())) + .withRel("data"); + view.add(dataLink); + + final var repLink = + WebMvcLinkBuilder.linkTo( + methodOn(RelationControllers.ArtifactsToRepresentations.class) + .getResource(artifact.getId(), null, null)) + .withRel("representations"); + view.add(repLink); + + final var agreementLink = + linkTo(methodOn(RelationControllers.ArtifactsToAgreements.class) + .getResource(artifact.getId(), null, null)) + .withRel("agreements"); + view.add(agreementLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, ArtifactController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/CatalogView.java b/src/main/java/io/dataspaceconnector/view/CatalogView.java new file mode 100644 index 000000000..abe654d3d --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/CatalogView.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.time.ZonedDateTime; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of catalog information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "catalogs", itemRelation = "catalog") +public class CatalogView extends RepresentationModel { + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * The title of the catalog. + */ + private String title; + + /** + * The description of the catalog. + */ + private String description; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/CatalogViewAssembler.java b/src/main/java/io/dataspaceconnector/view/CatalogViewAssembler.java new file mode 100644 index 000000000..a0258f885 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/CatalogViewAssembler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.CatalogController; +import io.dataspaceconnector.model.Catalog; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +/** + * Assembles the REST resource for a catalog. + */ +@Component +@NoArgsConstructor +public class CatalogViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the CatalogView from a Catalog. + * @param catalog The catalog. + * @return The new view. + */ + @Override + public CatalogView toModel(final Catalog catalog) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(catalog, CatalogView.class); + view.add(getSelfLink(catalog.getId())); + + final var offeredResLink = WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.CatalogsToOfferedResources.class) + .getResource(catalog.getId(), null, null)) + .withRel("offers"); + view.add(offeredResLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, CatalogController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/ContractRuleView.java b/src/main/java/io/dataspaceconnector/view/ContractRuleView.java new file mode 100644 index 000000000..31c014013 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ContractRuleView.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.time.ZonedDateTime; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of rule information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "rules", itemRelation = "rule") +public class ContractRuleView extends RepresentationModel { + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * The title of the rule. + */ + private String title; + + /** + * The ids rule as rdf string. + */ + private String value; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/ContractRuleViewAssembler.java b/src/main/java/io/dataspaceconnector/view/ContractRuleViewAssembler.java new file mode 100644 index 000000000..a9f5ddafe --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ContractRuleViewAssembler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.RuleController; +import io.dataspaceconnector.model.ContractRule; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +/** + * Assembles the REST resource for an contract rule. + */ +@Component +@NoArgsConstructor +public class ContractRuleViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the ContractRuleView from a ContractRule. + * @param rule The contract rule. + * @return The new view. + */ + @Override + public ContractRuleView toModel(final ContractRule rule) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(rule, ContractRuleView.class); + view.add(getSelfLink(rule.getId())); + + final var contractLink = WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.RulesToContracts.class) + .getResource(rule.getId(), null, null)) + .withRel("contracts"); + view.add(contractLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, RuleController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/ContractView.java b/src/main/java/io/dataspaceconnector/view/ContractView.java new file mode 100644 index 000000000..52e08561a --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ContractView.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of contract information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "contract", itemRelation = "contract") +public class ContractView extends RepresentationModel { + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * The title of the contract. + */ + private String title; + + /** + * The start date of the contract. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime start; + + /** + * The end data of the contract. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime end; + + /** + * The consumer addressed by the contract offer. + */ + private URI consumer; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/ContractViewAssembler.java b/src/main/java/io/dataspaceconnector/view/ContractViewAssembler.java new file mode 100644 index 000000000..4f3c400b0 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ContractViewAssembler.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.ContractController; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.utils.ErrorMessages; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +/** + * Assembles the REST resource for a contracts. + */ +@Component +@NoArgsConstructor +public class ContractViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the ContractView from a Contract. + * + * @param contract The contract. + * @return The new view. + */ + @Override + public ContractView toModel(final Contract contract) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(contract, ContractView.class); + view.add(getSelfLink(contract.getId())); + + final var rulesLink = WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.ContractsToRules.class) + .getResource(contract.getId(), null, null)) + .withRel("rules"); + view.add(rulesLink); + + final var resourceType = contract.getResources(); + Link resourceLinker; + if (resourceType.isEmpty()) { + // No elements found, default to offered resources + resourceLinker = linkTo(methodOn(RelationControllers.ContractsToOfferedResources.class) + .getResource(contract.getId(), null, null)) + .withRel("offers"); + } else { + // Construct the link for the right resource type. + if (resourceType.get(0) instanceof OfferedResource) { + resourceLinker = + linkTo(methodOn(RelationControllers.ContractsToOfferedResources.class) + .getResource(contract.getId(), null, null)) + .withRel("offers"); + } else if (resourceType.get(0) instanceof RequestedResource) { + resourceLinker = + linkTo(methodOn(RelationControllers.ContractsToRequestedResources.class) + .getResource(contract.getId(), null, null)) + .withRel("requests"); + } else { + throw new UnreachableLineException(ErrorMessages.UNKNOWN_TYPE); + } + } + + view.add(resourceLinker); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, ContractController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/OfferedResourceView.java b/src/main/java/io/dataspaceconnector/view/OfferedResourceView.java new file mode 100644 index 000000000..46cda0bef --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/OfferedResourceView.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of offered resource information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "resources", itemRelation = "resource") +public class OfferedResourceView extends RepresentationModel { + + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * The title of the resource. + */ + private String title; + + /** + * The description of the resource. + */ + private String description; + + /** + * The keywords of the resource. + */ + private List keywords; + + /** + * The publisher of the resource. + */ + private URI publisher; + + /** + * The language of the resource. + */ + private String language; + + /** + * The licence of the resource. + */ + private URI licence; + + /** + * The version of the resource. + */ + private long version; + + /** + * The owner of the resource. + */ + private URI sovereign; + + /** + * The endpoint of the resource. + */ + private URI endpointDocumentation; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/OfferedResourceViewAssembler.java b/src/main/java/io/dataspaceconnector/view/OfferedResourceViewAssembler.java new file mode 100644 index 000000000..005cd74de --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/OfferedResourceViewAssembler.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.OfferedResourceController; +import io.dataspaceconnector.model.OfferedResource; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +/** + * Assembles the REST resource for an offered resource. + */ +@Component +@NoArgsConstructor +public class OfferedResourceViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the OfferedResourceView from an OfferedResource. + * + * @param resource The resource. + * @return The new view. + */ + @Override + public OfferedResourceView toModel(final OfferedResource resource) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(resource, OfferedResourceView.class); + view.add(getSelfLink(resource.getId())); + + final var contractsLink = + WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.OfferedResourcesToContracts.class) + .getResource(resource.getId(), null, null)) + .withRel("contracts"); + view.add(contractsLink); + + final var repLink = + linkTo(methodOn(RelationControllers.OfferedResourcesToRepresentations.class) + .getResource(resource.getId(), null, null)) + .withRel("representations"); + view.add(repLink); + + final var catalogLink = + linkTo(methodOn(RelationControllers.OfferedResourcesToCatalogs.class) + .getResource(resource.getId(), null, null)) + .withRel("catalogs"); + view.add(catalogLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, OfferedResourceController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/RepresentationView.java b/src/main/java/io/dataspaceconnector/view/RepresentationView.java new file mode 100644 index 000000000..149d213f8 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/RepresentationView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.Map; + +/** + * A DTO for controlled exposing of representation information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "representations", itemRelation = "representation") +public class RepresentationView extends RepresentationModel { + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * Remote id. + */ + private URI remoteId; + + /** + * The title of the representation. + */ + private String title; + + /** + * The media type of the representation. + */ + private String mediaType; + + /** + * The language of the representation. + */ + private String language; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/RepresentationViewAssembler.java b/src/main/java/io/dataspaceconnector/view/RepresentationViewAssembler.java new file mode 100644 index 000000000..f46b91af2 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/RepresentationViewAssembler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.RepresentationController; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.utils.ErrorMessages; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +/** + * Assembles the REST resource for an representation. + */ +@Component +@NoArgsConstructor +public class RepresentationViewAssembler + implements RepresentationModelAssembler, SelfLinking { + /** + * Construct the RepresentationView from an Representation. + * + * @param representation The representation. + * @return The new view. + */ + @Override + public RepresentationView toModel(final Representation representation) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(representation, RepresentationView.class); + view.add(getSelfLink(representation.getId())); + + final var selfLink = + linkTo(RepresentationController.class).slash(representation.getId()).withSelfRel(); + view.add(selfLink); + + final var artifactsLink = + WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.RepresentationsToArtifacts.class) + .getResource(representation.getId(), null, null)) + .withRel("artifacts"); + view.add(artifactsLink); + + final var resourceType = representation.getResources(); + Link resourceLinker; + if (resourceType.isEmpty()) { + // No elements found, default to offered resources + resourceLinker = + linkTo(methodOn(RelationControllers.RepresentationsToOfferedResources.class) + .getResource(representation.getId(), null, null)) + .withRel("offers"); + } else { + // Construct the link for the right resource type. + if (resourceType.get(0) instanceof OfferedResource) { + resourceLinker = + linkTo(methodOn(RelationControllers.RepresentationsToOfferedResources.class) + .getResource(representation.getId(), null, null)) + .withRel("offers"); + } else if (resourceType.get(0) instanceof RequestedResource) { + resourceLinker = + linkTo(methodOn( + RelationControllers.RepresentationsToRequestedResources.class) + .getResource(representation.getId(), null, null)) + .withRel("requests"); + } else { + throw new UnreachableLineException(ErrorMessages.UNKNOWN_TYPE); + } + } + + view.add(resourceLinker); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, RepresentationController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/RequestedResourceView.java b/src/main/java/io/dataspaceconnector/view/RequestedResourceView.java new file mode 100644 index 000000000..82855a2e1 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/RequestedResourceView.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +/** + * A DTO for controlled exposing of requested resource information in API responses. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@Relation(collectionRelation = "requested", itemRelation = "resource") +public class RequestedResourceView extends RepresentationModel { + + /** + * The creation date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime creationDate; + + /** + * The last modification date. + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + private ZonedDateTime modificationDate; + + /** + * Remote id. + */ + private URI remoteId; + + /** + * The title of the resource. + */ + private String title; + + /** + * The description of the resource. + */ + private String description; + + /** + * The keywords of the resource. + */ + private List keywords; + + /** + * The publisher of the resource. + */ + private URI publisher; + + /** + * The language of the resource. + */ + private String language; + + /** + * The licence of the resource. + */ + private URI licence; + + /** + * The version of the resource. + */ + private long version; + + /** + * The owner of the resource. + */ + private URI sovereign; + + /** + * The endpoint of the resource. + */ + private URI endpointDocumentation; + + /** + * Additional properties. + */ + private Map additional; +} diff --git a/src/main/java/io/dataspaceconnector/view/RequestedResourceViewAssembler.java b/src/main/java/io/dataspaceconnector/view/RequestedResourceViewAssembler.java new file mode 100644 index 000000000..262218f88 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/RequestedResourceViewAssembler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers.RequestedResourceController; +import io.dataspaceconnector.model.RequestedResource; +import lombok.NoArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.stereotype.Component; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +/** + * Assembles the REST resource for a requested resource. + */ +@Component +@NoArgsConstructor +public class RequestedResourceViewAssembler + implements RepresentationModelAssembler, + SelfLinking { + /** + * Construct the RequestedResourceView from a RequestedResource. + * + * @param resource The resource. + * @return The new view. + */ + @Override + public RequestedResourceView toModel(final RequestedResource resource) { + final var modelMapper = new ModelMapper(); + final var view = modelMapper.map(resource, RequestedResourceView.class); + view.add(getSelfLink(resource.getId())); + + final var contractsLink = + WebMvcLinkBuilder + .linkTo(methodOn(RelationControllers.RequestedResourcesToContracts.class) + .getResource(resource.getId(), null, null)) + .withRel("contracts"); + view.add(contractsLink); + + final var representationLink = + linkTo(methodOn(RelationControllers.RequestedResourcesToRepresentations.class) + .getResource(resource.getId(), null, null)) + .withRel("representations"); + view.add(representationLink); + + final var catalogLink = + linkTo(methodOn(RelationControllers.RequestedResourcesToCatalogs.class) + .getResource(resource.getId(), null, null)) + .withRel("catalogs"); + view.add(catalogLink); + + return view; + } + + @Override + public final Link getSelfLink(final UUID entityId) { + return ViewAssemblerHelper.getSelfLink(entityId, RequestedResourceController.class); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/SelfLinking.java b/src/main/java/io/dataspaceconnector/view/SelfLinking.java new file mode 100644 index 000000000..67d432276 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/SelfLinking.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import org.springframework.hateoas.Link; + +import java.util.UUID; + +/** + * Interface for generating the self-link of entities. + */ +public interface SelfLinking { + /** + * Construct self-link of an entity. + * + * @param entityId The id of the entity. + * @return The self-link of the entity. + */ + Link getSelfLink(UUID entityId); +} diff --git a/src/main/java/io/dataspaceconnector/view/ViewAssemblerHelper.java b/src/main/java/io/dataspaceconnector/view/ViewAssemblerHelper.java new file mode 100644 index 000000000..1071f7d90 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/ViewAssemblerHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import org.springframework.hateoas.Link; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +/** + * Helper for building self-links. + */ +public final class ViewAssemblerHelper { + /** + * Default constructor. + */ + private ViewAssemblerHelper() { + // Nothing to do here. Intentionally empty. + } + + /** + * Build self-link for entity. + * + * @param entityId The entity id. + * @param tClass The controller class for managing the entity class. + * @param Type of the entity. + * @return The self-link of the entity. + * @throws IllegalArgumentException if the class is null. + */ + public static Link getSelfLink(final UUID entityId, final Class tClass) { + return linkTo(tClass).slash(entityId).withSelfRel(); + } +} diff --git a/src/main/java/io/dataspaceconnector/view/package-info.java b/src/main/java/io/dataspaceconnector/view/package-info.java new file mode 100644 index 000000000..55783a164 --- /dev/null +++ b/src/main/java/io/dataspaceconnector/view/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The views are DTOs for controlled exposing of + * Dataspace Connector entities. + */ +package io.dataspaceconnector.view; diff --git a/src/main/lombok.config b/src/main/lombok.config new file mode 100644 index 000000000..df71bb6a0 --- /dev/null +++ b/src/main/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c742a1bab..40063dfa8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,29 +1,73 @@ -######################################################################################################################## -## Dataspace Connector ## -######################################################################################################################## +#################################################################################################### +## Dataspace Connector ## +#################################################################################################### ## Spring Tomcat server.port=8080 +## General Information +spring.application.name=Dataspace Connector +spring.banner.location=classpath:banner.txt + +title=@project.name@ +version=@project.version@ +project_desc=@project.description@ +organization_name=@project.organization.name@ +contact_url=@project.url@ +contact_email=@email@ +licence=@licence_name@ +licence_url=@licence_url@ + +## Spring deserialization +spring.jackson.deserialization.fail-on-unknown-properties=true + ## Spring Security spring.security.user.name=admin spring.security.user.password=password ## OpenAPI -springdoc.swagger-ui.path=/admin/api -springdoc.swagger-ui.operationsSorter=method +springdoc.swagger-ui.path=/api/docs +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.disable-swagger-default-url=true -## TLS -server.ssl.enabled=true -server.ssl.key-store-policyType=PKCS12 -server.ssl.key-store=classpath:conf/keystore-localhost.p12 -server.ssl.key-store-password=password -server.ssl.key-alias=1 -#security.require-ssl=true +## Endpoints +management.endpoints.enabled-by-default=false +#management.endpoints.web.exposure.include=logfile, loggers +#management.endpoint.loggers.enabled=true +#management.endpoint.logfile.enabled=true +#management.endpoint.logfile.external-file=./log/dataspaceconnector.log + +## Jaeger +opentracing.jaeger.udp-sender.host=localhost +opentracing.jaeger.udp-sender.port=6831 +opentracing.jaeger.log-spans=true + +#################################################################################################### +## IDS Properties ## +#################################################################################################### + +## Configuration Properties +configuration.path=conf/config.json +configuration.keyStorePassword=password +configuration.keyAlias=1 +configuration.trustStorePassword=password + +## DAPS +daps.token.url=https://daps.aisec.fraunhofer.de +daps.key.url=https://daps.aisec.fraunhofer.de/v2/.well-known/jwks.json -######################################################################################################################## -## Storage ## -######################################################################################################################## +## Clearing House +clearing.house.url=https://ch-ids.aisec.fraunhofer.de/logs/messages/ + +## Connector Settings +policy.negotiation=true +policy.allow-unsupported-patterns=false +policy.framework=INTERNAL +# policy.framework=MYDATA + +#################################################################################################### +## Storage ## +#################################################################################################### ### H2 Database spring.datasource.url=jdbc:h2:file:./target/db/resources @@ -32,20 +76,32 @@ spring.datasource.username=sa spring.datasource.password=password ## Enable H2 Console Access -spring.h2.console.enabled=true -spring.h2.console.path=/admin/h2 +spring.h2.console.enabled=false +spring.h2.console.path=/database spring.h2.console.settings.web-allow-others=true ## Import Data #spring.datasource.data=classpath:/data/data.sql ### Hibernate Properties -# spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy spring.jpa.hibernate.ddl-auto=update -# spring.jpa.hibernate.ddl-auto=create -# Hibernate Logging -logging.level.org.hibernate.SQL= DEBUG +## Disable open in view transactions +spring.jpa.open-in-view=true + +#################################################################################################### +## HTTP/S ## +#################################################################################################### + +# server.http2.enabled=true + +## TLS +server.ssl.enabled=true +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:conf/keystore-localhost.p12 +server.ssl.key-store-password=password +server.ssl.key-alias=1 +#security.require-ssl=true ## MULTIPART (MultipartProperties) spring.servlet.multipart.enabled=true @@ -53,16 +109,10 @@ spring.servlet.multipart.file-size-threshold=2KB spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-request-size=215MB -######################################################################################################################## -## IDS Properties ## -######################################################################################################################## +## Timeout settings (millis) +http.timeout.connect=10000 +http.timeout.read=10000 +http.timeout.write=10000 +http.timeout.call=10000 -## Configuration Properties -configuration.path=conf/config.json -configuration.keyStorePassword=password -configuration.keyAlias=1 -configuration.trustStorePassword=password - -## DAPS -daps.token.url=https://daps.aisec.fraunhofer.de -daps.key.url=https://daps.aisec.fraunhofer.de/.well-known/jwks.json +httptrace.enabled=false diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 000000000..872a3a6a7 --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + ____ _ ____ _ + | _ \ __ _ | |_ __ _ ___ _ __ __ _ ___ ___ / ___| ___ _ __ _ __ ___ ___ | |_ ___ _ __ + | | | | / _` | | __| / _` | / __| | '_ \ / _` | / __| / _ \ | | / _ \ | '_ \ | '_ \ / _ \ / __| | __| / _ \ | '__| + | |_| | | (_| | | |_ | (_| | \__ \ | |_) | | (_| | | (__ | __/ | |___ | (_) | | | | | | | | | | __/ | (__ | |_ | (_) | | | + |____/ \__,_| \__| \__,_| |___/ | .__/ \__,_| \___| \___| \____| \___/ |_| |_| |_| |_| \___| \___| \__| \___/ |_| + Dataspace Connector |_| Version ${application.version} diff --git a/src/main/resources/conf/config.json b/src/main/resources/conf/config.json index 629b108e3..99718c4d3 100644 --- a/src/main/resources/conf/config.json +++ b/src/main/resources/conf/config.json @@ -26,16 +26,16 @@ "@value" : "IDS Connector with static example resources hosted by the Fraunhofer ISST", "@type" : "http://www.w3.org/2001/XMLSchema#string" } ], - "ids:version" : "v3.0.0", + "ids:version" : "@project.version@", "ids:hasDefaultEndpoint" : { "@type" : "ids:ConnectorEndpoint", "@id" : "https://w3id.org/idsa/autogen/connectorEndpoint/e5e2ab04-633a-44b9-87d9-a097ae6da3cf", "ids:accessURL" : { - "@id" : "/api/ids/data" + "@id" : "https://localhost:8080/api/ids/data" } }, - "ids:outboundModelVersion" : "4.0.0", - "ids:inboundModelVersion" : [ "4.0.0" ], + "ids:outboundModelVersion" : "4.0.4", + "ids:inboundModelVersion" : [ "4.0.0", "4.0.4" ], "ids:title" : [ { "@value" : "Dataspace Connector", "@type" : "http://www.w3.org/2001/XMLSchema#string" @@ -58,21 +58,5 @@ }, "ids:keyStore" : { "@id" : "file:///conf/keystore-localhost.p12" - }, - "ids:connectorProxy" : [ { - "@type" : "ids:Proxy", - "@id" : "https://w3id.org/idsa/autogen/proxy/548dc73a-ccfb-4039-9569-4b8e219b90bc", - "ids:proxyAuthentication" : { - "@type" : "ids:BasicAuthentication", - "@id" : "https://w3id.org/idsa/autogen/basicAuthentication/47e3cd59-d351-4f5b-99fc-561c94bad5e1" - }, - "ids:proxyURI" : { - "@id" : "http://proxy.dortmund.isst.fraunhofer.de:3128" - }, - "ids:noProxy" : [ { - "@id" : "https://localhost:8080/" - }, { - "@id" : "http://localhost:8080/" - } ] - } ] + } } diff --git a/src/main/resources/examples/resource.txt b/src/main/resources/examples/resource.txt deleted file mode 100644 index 79b9475ba..000000000 --- a/src/main/resources/examples/resource.txt +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Sample Resource", - "description": "This is an example resource containing weather data", - "keywords": [ - "weather", - "data" - ], - "policy": { - "@context": "http://www.w3.org/ns/odrl.jsonld", - "@type": "Agreement", - "uid": "http://example.com/policy", - "permission": [{ - "action": "use" - }] - }, - "owner": "https://openweathermap.org/", - "license": "ODbL", - "version": "1.0", - "representations": [ - { - "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "type": "json", - "byteSize": 105, - "sourceType": "http-get", - "source": { - "url": "https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02", - "username": "-", - "password": "-", - "system": "Open Weather Map API" - } - } - ] -} diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml deleted file mode 100644 index 72eb421c9..000000000 --- a/src/main/resources/log4j.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 000000000..46b52320d --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,36 @@ + + + + + + + + + %d{ISO8601} [%thread] %-5p %encode{%.-10000m}{CRLF}%n + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplicationTests.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplicationTests.java deleted file mode 100644 index f7a24a000..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplicationTests.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ConnectorApplicationTests { - - @Test - void contextLoads() { - } -} diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/ArtifactRequestMessageHandlingTest.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/ArtifactRequestMessageHandlingTest.java deleted file mode 100644 index 7c5b966ea..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/ArtifactRequestMessageHandlingTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.integrationtest; - -import de.fraunhofer.iais.eis.*; -import de.fraunhofer.iais.eis.ids.jsonld.Serializer; -import de.fraunhofer.isst.dataspaceconnector.message.ArtifactMessageHandler; -import de.fraunhofer.isst.dataspaceconnector.model.BackendSource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import de.fraunhofer.isst.ids.framework.util.MultipartStringParser; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -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.mock.mockito.MockBean; -import org.springframework.mock.web.MockPart; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import javax.transaction.Transactional; -import java.lang.reflect.Field; -import java.net.URI; -import java.util.*; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -/** - * This class tests the correct handling of ArtifactRequestMessages. - * - * @author Ronja Quensel - * @version $Id: $Id - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class ArtifactRequestMessageHandlingTest { - - private final String idsMessageEndpoint = "/api/ids/data"; - - private final String HEADER_MULTIPART_NAME = "header"; - - private final String PAYLOAD_MULTIPART_NAME = "payload"; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private OfferedResourceService offeredResourceService; - - @Autowired - private Serializer serializer; - - @MockBean - private PolicyHandler policyHandler; - - @Autowired - private ArtifactMessageHandler artifactMessageHandler; - - @Autowired - private TokenProvider tokenProvider; - - private final UUID representationUUID = UUID.fromString("f7f69b0e-0930-11eb-adc1-0242ac120002"); - - @Before - public void init() throws Exception { - MockitoAnnotations.initMocks(this); - - Field policyHandlerField = ArtifactMessageHandler.class.getDeclaredField("policyHandler"); - policyHandlerField.setAccessible(true); - policyHandlerField.set(artifactMessageHandler, policyHandler); - } - - @Test - @Transactional - public void requestArtifact_validId_provisionAllowed() throws Exception { - when(policyHandler.onDataProvision(any())).thenReturn(true); - - String data = "Hi, I'm data!"; - UUID resourceId = offeredResourceService.addResource(getResourceMetadata()); - offeredResourceService.addData(resourceId, data); - - MockPart header = - new MockPart(HEADER_MULTIPART_NAME, getArtifactRequestMessageHeader(representationUUID).getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - String responsePayload = multipart.get(PAYLOAD_MULTIPART_NAME); - - serializer.deserialize(responseHeader, ArtifactResponseMessage.class); - Assert.assertEquals(data, responsePayload); - - offeredResourceService.deleteResource(resourceId); - } - - @Test - @Transactional - public void requestArtifact_validId_provisionInhibited() throws Exception { - when(policyHandler.onDataProvision(any())).thenReturn(false); - - UUID resourceId = offeredResourceService.addResource(getResourceMetadata()); - offeredResourceService.addData(resourceId, "data"); - - MockPart header = - new MockPart(HEADER_MULTIPART_NAME, getArtifactRequestMessageHeader(representationUUID).getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - - RejectionMessage rejectionMessage = serializer.deserialize(responseHeader, RejectionMessage.class); - Assert.assertEquals(RejectionReason.NOT_AUTHORIZED, rejectionMessage.getRejectionReason()); - - offeredResourceService.deleteResource(resourceId); - } - - @Test - public void requestArtifact_invalidId() throws Exception { - MockPart header = - new MockPart(HEADER_MULTIPART_NAME, getArtifactRequestMessageHeader(representationUUID).getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - - RejectionMessage rejectionMessage = serializer.deserialize(responseHeader, RejectionMessage.class); - Assert.assertEquals(RejectionReason.NOT_FOUND, rejectionMessage.getRejectionReason()); - } - - private ResourceMetadata getResourceMetadata() { - ResourceRepresentation representation = new ResourceRepresentation(representationUUID, "text/plain", - 123, ResourceRepresentation.SourceType.LOCAL, new BackendSource(URI.create("http://uri.com"), - "userName", "pasword", "system")); - representation.setUuid(representationUUID); - return new ResourceMetadata("Test resource", "", Arrays.asList("test", "resource"), "policy", - URI.create("http://resource-owner.com"), URI.create("http://license.com"), "v1.0", - Collections.singletonList(representation)); - } - - private String getArtifactRequestMessageHeader(UUID requestedArtifact) { - return "{\n" + - " \"@context\":\"https://w3id.org/idsa/contexts/2.0.0/context.jsonld\",\r\n" + - " \"@type\":\"ids:ArtifactRequestMessage\",\r\n" + - " \"ids:modelVersion\":\"3.1.0\",\r\n" + - " \"ids:issued\":\"2019-12-10T10:31:28.119+01:00\",\r\n" + - " \"ids:issuerConnector\":\"https://simpleconnector.ids.isst.fraunhofer.de/\",\r\n" + - " \"ids:requestedArtifact\":\"https://w3id.org/idsa/autogen/dataResource/" + requestedArtifact + "\",\r\n" + - " \"ids:securityToken\":{\r\n" + - " \"@type\":\"ids:DynamicAttributeToken\",\r\n" + - " \"ids:tokenValue\":\"" + tokenProvider.provideDapsToken() + "\",\r\n" + - " \"referingConnector\":\"https://divaconnector.isst.fraunhofer.de\",\r\n" + - " \"aud\":\"asd\",\r\n" + - " \"iss\":\"adas\",\r\n" + - " \"sub\":\"sadas\",\r\n" + - " \"nbf\":\"asdasd\",\r\n" + - " \"exp\":\"1789722984\",\r\n" + - " \"ids:tokenFormat\":{\r\n" + - " \"@id\":\"https://w3id.org/idsa/code/JWT\"\r\n" + - " },\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/dynamicAttributeToken/260ce6a6-3738-4cd2-9705-113301fdb111\"\r\n" + - " },\r\n" + - " \"ids:contentVersion\":\"3.1.0\",\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/artifactRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - "}\r\n"; - } - -} diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/DescriptionRequestMessageHandlingTest.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/DescriptionRequestMessageHandlingTest.java deleted file mode 100644 index cae1395d8..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/DescriptionRequestMessageHandlingTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.integrationtest; - -import de.fraunhofer.iais.eis.Connector; -import de.fraunhofer.iais.eis.DescriptionResponseMessage; -import de.fraunhofer.iais.eis.RejectionMessage; -import de.fraunhofer.iais.eis.RejectionReason; -import de.fraunhofer.iais.eis.ids.jsonld.Serializer; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceRepository; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.ids.framework.spring.starter.TokenProvider; -import de.fraunhofer.isst.ids.framework.util.MultipartStringParser; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -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.mock.web.MockPart; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import javax.transaction.Transactional; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.UUID; - -/** - * This class tests the correct handling of DescriptionRequestMessages. - * - * @author Ronja Quensel - * @version $Id: $Id - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class DescriptionRequestMessageHandlingTest { - - private final String idsMessageEndpoint = "/api/ids/data"; - - private final String HEADER_MULTIPART_NAME = "header"; - - private final String PAYLOAD_MULTIPART_NAME = "payload"; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private OfferedResourceService offeredResourceService; - - @Autowired - private OfferedResourceRepository offeredResourceRepository; - - @Autowired - private Serializer serializer; - - @Autowired - private TokenProvider tokenProvider; - - @Test - public void requestSelfDescription() throws Exception { - MockPart header = new MockPart(HEADER_MULTIPART_NAME, getHeaderRequestedElementNull().getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - String responsePayload = multipart.get(PAYLOAD_MULTIPART_NAME); - - serializer.deserialize(responseHeader, DescriptionResponseMessage.class); - serializer.deserialize(responsePayload, Connector.class); - } - - @Test - @Transactional - public void requestArtifactDescription_validId() throws Exception { - UUID resourceId = offeredResourceService.addResource(getResourceMetadata()); - offeredResourceService.addData(resourceId, "data"); - - MockPart header = new MockPart(HEADER_MULTIPART_NAME, getHeaderRequestedElementNotNull(resourceId).getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - String responsePayload = multipart.get(PAYLOAD_MULTIPART_NAME); - - serializer.deserialize(responseHeader, DescriptionResponseMessage.class); - Assert.assertEquals(offeredResourceService.getOfferedResources().get(resourceId).toRdf(), responsePayload); - } - - @Test - @Transactional - public void requestArtifactDescription_invalidId() throws Exception { - offeredResourceRepository.deleteAll(); - - MockPart header = new MockPart(HEADER_MULTIPART_NAME, getHeaderRequestedElementNotNull(UUID.randomUUID()).getBytes()); - MockPart payload = new MockPart(PAYLOAD_MULTIPART_NAME, "".getBytes()); - - String response = mockMvc.perform(MockMvcRequestBuilders - .multipart(idsMessageEndpoint) - .part(header, payload)) - .andReturn().getResponse().getContentAsString(); - - Map multipart = MultipartStringParser.stringToMultipart(response); - String responseHeader = multipart.get(HEADER_MULTIPART_NAME); - - RejectionMessage rejectionMessage = serializer.deserialize(responseHeader, RejectionMessage.class); - Assert.assertEquals(RejectionReason.NOT_FOUND, rejectionMessage.getRejectionReason()); - } - - private ResourceMetadata getResourceMetadata() { - return new ResourceMetadata("Test resource", "", Arrays.asList("test", "resource"), - "{\n" + - " \"@context\" : {\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\"\n" + - " },\n" + - " \"@type\" : \"ids:ContractOffer\",\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/contractOffer/110e659b-d171-4519-8a65-8a2c297ec296\",\n" + - " \"ids:permission\" : [ {\n" + - " \"@type\" : \"ids:Permission\",\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/permission/7e1a166d-8c42-492f-afb8-204cea7aacf6\",\n" + - " \"ids:description\" : [ {\n" + - " \"@value\" : \"provide-access\",\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + - " } ],\n" + - " \"ids:action\" : [ {\n" + - " \"@id\" : \"idsc:USE\"\n" + - " } ],\n" + - " \"ids:title\" : [ {\n" + - " \"@value\" : \"Example Usage Policy\",\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + - " } ]\n" + - " } ]\n" + - "}", - URI.create("http://resource-owner.com"), URI.create("http://license.com"), "v1.0", - new ArrayList<>()); - } - - private String getHeaderRequestedElementNull() { - return "{\r\n" + - " \"@context\":\"https://w3id.org/idsa/contexts/2.0.0/context.jsonld\",\r\n" + - " \"@type\":\"ids:DescriptionRequestMessage\",\r\n" + - " \"ids:modelVersion\":\"4.0.0\",\r\n" + - " \"ids:issued\":\"2019-12-10T10:31:28.119+01:00\",\r\n" + - " \"ids:issuerConnector\":\"https://simpleconnector.ids.isst.fraunhofer.de/\",\r\n" + - " \"ids:requestedElement\":null,\r\n" + - " \"ids:securityToken\":{\r\n" + - " \"@type\":\"ids:DynamicAttributeToken\",\r\n" + - " \"ids:tokenValue\":\"" + tokenProvider.provideDapsToken() + "\",\r\n" + - " \"referingConnector\":\"https://divaconnector.isst.fraunhofer.de\",\r\n" + - " \"aud\":\"asd\",\r\n" + - " \"iss\":\"adas\",\r\n" + - " \"sub\":\"sadas\",\r\n" + - " \"nbf\":\"asdasd\",\r\n" + - " \"exp\":\"1789722984\",\r\n" + - " \"ids:tokenFormat\":{\r\n" + - " \"@id\":\"https://w3id.org/idsa/code/JWT\"\r\n" + - " },\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/dynamicAttributeToken/260ce6a6-3738-4cd2-9705-113301fdb111\"\r\n" + - " },\r\n" + - " \"ids:contentVersion\":\"4.0.0\",\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/descriptionRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - "}\r\n"; - } - - private String getHeaderRequestedElementNotNull(UUID requestedArtifact) { - return "{\r\n" + - " \"@context\":\"https://w3id.org/idsa/contexts/2.0.0/context.jsonld\",\r\n" + - " \"@type\":\"ids:DescriptionRequestMessage\",\r\n" + - " \"ids:modelVersion\":\"4.0.0\",\r\n" + - " \"ids:issued\":\"2019-12-10T10:31:28.119+01:00\",\r\n" + - " \"ids:issuerConnector\":\"https://simpleconnector.ids.isst.fraunhofer.de/\",\r\n" + - " \"ids:requestedElement\":\"https://w3id.org/idsa/autogen/dataResource/" + requestedArtifact.toString() + "\",\r\n" + - " \"ids:securityToken\":{\r\n" + - " \"@type\":\"ids:DynamicAttributeToken\",\r\n" + - " \"ids:tokenValue\":\"" + tokenProvider.provideDapsToken() + "\",\r\n" + - " \"referingConnector\":\"https://divaconnector.isst.fraunhofer.de\",\r\n" + - " \"aud\":\"asd\",\r\n" + - " \"iss\":\"adas\",\r\n" + - " \"sub\":\"sadas\",\r\n" + - " \"nbf\":\"asdasd\",\r\n" + - " \"exp\":\"1789722984\",\r\n" + - " \"ids:tokenFormat\":{\r\n" + - " \"@id\":\"https://w3id.org/idsa/code/JWT\"\r\n" + - " },\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/dynamicAttributeToken/260ce6a6-3738-4cd2-9705-113301fdb111\"\r\n" + - " },\r\n" + - " \"ids:contentVersion\":\"4.0.0\",\r\n" + - " \"@id\":\"https://w3id.org/idsa/autogen/descriptionRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - "}\r\n"; - } - -} diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestArtifactTest.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestArtifactTest.java deleted file mode 100644 index 058ef7992..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestArtifactTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.integrationtest; - -import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestService; -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceImpl; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceRepository; -import de.fraunhofer.isst.ids.framework.spring.starter.IDSHttpService; -import okhttp3.*; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -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.mock.mockito.MockBean; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.lang.reflect.Field; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.UUID; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -/** - * This class tests whether the connecter can request and save data from other connectors. - * - * @author Ronja Quensel - * @version $Id: $Id - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class RequestArtifactTest { - - private final String requestArtifactEndpoint = "/admin/api/request/artifact"; - - @Autowired - private MockMvc mockMvc; - - @MockBean - private IDSHttpService idsHttpService; - - @Autowired - private RequestedResourceRepository requestedResourceRepository; - - @Autowired - private ConnectorRequestService connectorRequestService; - - private URI recipient; - - private URI requestedArtifact; - - private final String data = "Hi, I'm data!"; - - @Before - public void init() throws Exception { - recipient = new URI("http://recipient-uri.com"); - requestedArtifact = new URI("https://w3id.org/idsa/autogen/dataResource/7434f738-87f8-45c7-adad-14fdb09bc931"); - - MockitoAnnotations.initMocks(this); - - Field idsHttpServiceField = ConnectorRequestServiceImpl.class.getDeclaredField("idsHttpService"); - idsHttpServiceField.setAccessible(true); - idsHttpServiceField.set(connectorRequestService, this.idsHttpService); - } - - @Test - @WithMockUser(roles = {"ADMIN"}) - public void requestArtifact() throws Exception { - requestedResourceRepository.deleteAll(); - - UUID key = requestedResourceRepository.save(getRequestedResource()).getUuid(); - Assert.assertTrue(requestedResourceRepository.findAll().get(0).getData().isEmpty()); - - when(idsHttpService.send(any(RequestBody.class), any(URI.class))) - .thenReturn(getResponse(getArtifactResponseMultipart())); - - mockMvc.perform(MockMvcRequestBuilders - .post(requestArtifactEndpoint) - .param("recipient", recipient.toString()) - .param("requestedArtifact", requestedArtifact.toString()) - .param("key", key.toString())); - - Assert.assertEquals(1, requestedResourceRepository.findAll().size()); - Assert.assertEquals(data, requestedResourceRepository.findAll().get(0).getData()); - } - - private Response getResponse(String multipartPayload) { - MediaType MEDIA_TYPE_MULTIPART = - MediaType.parse("multipart/form-data; boundary=6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh; charset=UTF-8"); - - ResponseBody responseBody = ResponseBody.create(multipartPayload, MEDIA_TYPE_MULTIPART); - return new Response.Builder() - .code(0) - .request(new Request.Builder().url("http://recipient-uri.com").build()) - .protocol(Protocol.HTTP_2) - .message("") - .body(responseBody) - .build(); - } - - private RequestedResource getRequestedResource() { - return new RequestedResource(new Date(), new Date(), getResourceMetadata(), "", 0); - } - - private ResourceMetadata getResourceMetadata() { - return new ResourceMetadata("Test resource", "", Arrays.asList("test", "resource"), "policy", - URI.create("http://resource-owner.com"), URI.create("http://license.com"), "v1.0", - new ArrayList<>()); - } - - private String getArtifactResponseMultipart() { - return "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"header\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 2110\r\n" + - "\r\n" + - "{\r\n" + - " \"@context\" : {\r\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\",\r\n" + - " \"idsc\" : \"https://w3id.org/idsa/code/\"\r\n" + - " },\r\n" + - " \"@type\" : \"ids:ArtifactResponseMessage\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifactResponseMessage/3291aeda-cc13-407e-98ff-4a661eda0618\",\r\n" + - " \"ids:modelVersion\" : \"3.1.0\",\r\n" + - " \"ids:issued\" : {\r\n" + - " \"@value\" : \"2020-10-07T10:51:49.782Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:issuerConnector\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:recipientConnector\" : [ {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " } ],\r\n" + - " \"ids:securityToken\" : {\r\n" + - " \"@type\" : \"ids:DynamicAttributeToken\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/dynamicAttributeToken/6ce8a1a3-6a79-4545-b0ef-35029bf23656\",\r\n" + - " \"ids:tokenValue\" : \"eyJ0eXAiOiJKV1QiLCJraWQiOiJkZWZhdWx0IiwiYWxnIjoiUlMyNTYifQ.eyJpZHNfYXR0cmlidXRlcyI6eyJzZWN1cml0eV9wcm9maWxlIjp7ImF1ZGl0X2xvZ2dpbmciOjB9LCJtZW1iZXJzaGlwIjp0cnVlLCJpZHMtdXJpIjoiaHR0cDovL3NvbWUtdXJpIiwidHJhbnNwb3J0X2NlcnRzX3NoYTI1OCI6ImJhY2I4Nzk1NzU3MzBiYjA4M2YyODNmZDViNjdhOGNiODk2OTQ0ZDFiZTI4YzdiMzIxMTdjZmM3NTdjODFlOTYifSwic2NvcGVzIjpbImlkc19jb25uZWN0b3IiXSwiYXVkIjoiSURTX0Nvbm5lY3RvciIsImlzcyI6Imh0dHBzOi8vZGFwcy5haXNlYy5mcmF1bmhvZmVyLmRlIiwic3ViIjoiQz1ERSxPPUZyYXVuaG9mZXIsT1U9SVNTVCxDTj01ODc3NmViZS1mOGY4LTRhNmYtYjQ0Yi1lZWVmYTQ3ZmMwNGIiLCJuYmYiOjE2MDIxNDQwNzMsImV4cCI6MTYwMjE0NzY3M30.SG1Av3G00ne2tYQMerrJbhg9f24klDMjS5ur1aykIGHrL5AyL2wsLit_5aMhG12DUQ7tPa2o4RHyTCQFAhVKkI9_bwCR9jGBcN6jfVn8vjxQ3mDvNdWOoRURI_3YOAjBlo1TqFLOKBmN3uTsB_ns7LqJDruea07sme5O38NOukHPWxsAnoiH4N9NByxHqxayrFj0buDxJCLKXG3_FQtZBcsGO89geylFec0epehh9pL5QV5nr4xLzVhfrJRgx512KVqr1hNLqfNRWGl0TFoKHyEE5J8IMEihZwF76_4kl_1HZe1HP866yO8ceONfTvRI2sCXmKpP8A02NGhisEF_Mg\",\r\n" + - " \"ids:tokenFormat\" : {\r\n" + - " \"@id\" : \"idsc:JWT\"\r\n" + - " }\r\n" + - " },\r\n" + - " \"ids:senderAgent\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:correlationMessage\" : {\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifactRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - " }\r\n" + - "}\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"payload\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 12\r\n" + - "\r\n" + - data + "\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh--\r\n"; - } - -} diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestDescriptionTest.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestDescriptionTest.java deleted file mode 100644 index 1729fb419..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/RequestDescriptionTest.java +++ /dev/null @@ -1,480 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.integrationtest; - -import de.fraunhofer.iais.eis.Connector; -import de.fraunhofer.iais.eis.ids.jsonld.Serializer; -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestService; -import de.fraunhofer.isst.dataspaceconnector.services.communication.ConnectorRequestServiceImpl; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceRepository; -import de.fraunhofer.isst.ids.framework.spring.starter.IDSHttpService; -import de.fraunhofer.isst.ids.framework.util.MultipartStringParser; -import okhttp3.*; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -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.mock.mockito.MockBean; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.lang.reflect.Field; -import java.net.URI; -import java.util.Map; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -/** - * This class tests whether the connecter can request and save metadata from other connectors. - * - * @author Ronja Quensel - * @version $Id: $Id - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class RequestDescriptionTest { - - private final String requestDescriptionEndpoint = "/admin/api/request/description"; - - @Autowired - private MockMvc mockMvc; - - @MockBean - private IDSHttpService idsHttpService; - - @Autowired - private RequestedResourceRepository requestedResourceRepository; - - @Autowired - private ConnectorRequestService connectorRequestService; - - @Autowired - private Serializer serializer; - - private URI recipient; - - private URI requestedArtifact; - - @Before - public void init() throws Exception { - recipient = new URI("http://recipient-uri.com"); - requestedArtifact = new URI("https://w3id.org/idsa/autogen/dataResource/7434f738-87f8-45c7-adad-14fdb09bc931"); - - MockitoAnnotations.initMocks(this); - - Field idsHttpServiceField = ConnectorRequestServiceImpl.class.getDeclaredField("idsHttpService"); - idsHttpServiceField.setAccessible(true); - idsHttpServiceField.set(connectorRequestService, this.idsHttpService); - } - - @Test - @WithMockUser(roles = {"ADMIN"}) - public void requestSelfDescription() throws Exception { - when(idsHttpService.send(any(RequestBody.class), any(URI.class))) - .thenReturn(getResponse(getSelfDescriptionMultipart())); - - String response = mockMvc.perform(MockMvcRequestBuilders - .post(requestDescriptionEndpoint) - .param("recipient", recipient.toString())) - .andReturn().getResponse().getContentAsString(); - - //remove first line ("Success: true/false") and first chars of next line ("Body: ") from response - response = response.substring(response.indexOf('\n') + 1).substring(6); - Map multipart = MultipartStringParser.stringToMultipart(response); - String payload = multipart.get("payload"); - - serializer.deserialize(payload, Connector.class); - } - - @Test - @WithMockUser(roles = {"ADMIN"}) - public void requestArtifactDescription() throws Exception { - if (!requestedResourceRepository.findAll().isEmpty()) { - requestedResourceRepository.deleteAll(); - } - - when(idsHttpService.send(any(RequestBody.class), any(URI.class))) - .thenReturn(getResponse(getArtifactDescriptionMultipart())); - - mockMvc.perform(MockMvcRequestBuilders - .post(requestDescriptionEndpoint) - .param("recipient", recipient.toString()) - .param("requestedArtifact", requestedArtifact.toString())); - - Assert.assertEquals(1, requestedResourceRepository.findAll().size()); - Assert.assertTrue(requestedResourceRepository.findAll().get(0).getData().isEmpty()); - } - - private Response getResponse(String multipartPayload) { - MediaType MEDIA_TYPE_MULTIPART = - MediaType.parse("multipart/form-data; boundary=6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh; charset=UTF-8"); - - ResponseBody responseBody = ResponseBody.create(multipartPayload, MEDIA_TYPE_MULTIPART); - return new Response.Builder() - .code(0) - .request(new Request.Builder().url("http://recipient-uri.com").build()) - .protocol(Protocol.HTTP_2) - .message("") - .body(responseBody) - .build(); - } - - private String getSelfDescriptionMultipart() { - return "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"header\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 2119\r\n" + - "\r\n" + - "{\r\n" + - " \"@context\" : {\r\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\",\r\n" + - " \"idsc\" : \"https://w3id.org/idsa/code/\"\r\n" + - " },\r\n" + - " \"@type\" : \"ids:DescriptionResponseMessage\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/descriptionResponseMessage/eb7747aa-8cf5-4d97-8c5f-d07e670dc7ec\",\r\n" + - " \"ids:modelVersion\" : \"3.1.0\",\r\n" + - " \"ids:issued\" : {\r\n" + - " \"@value\" : \"2020-10-07T08:22:51.117Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:issuerConnector\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:recipientConnector\" : [ {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " } ],\r\n" + - " \"ids:securityToken\" : {\r\n" + - " \"@type\" : \"ids:DynamicAttributeToken\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/dynamicAttributeToken/5ab6804f-900a-471c-a1d1-7dee49f90695\",\r\n" + - " \"ids:tokenValue\" : \"eyJ0eXAiOiJKV1QiLCJraWQiOiJkZWZhdWx0IiwiYWxnIjoiUlMyNTYifQ.eyJpZHNfYXR0cmlidXRlcyI6eyJzZWN1cml0eV9wcm9maWxlIjp7ImF1ZGl0X2xvZ2dpbmciOjB9LCJtZW1iZXJzaGlwIjp0cnVlLCJpZHMtdXJpIjoiaHR0cDovL3NvbWUtdXJpIiwidHJhbnNwb3J0X2NlcnRzX3NoYTI1OCI6ImJhY2I4Nzk1NzU3MzBiYjA4M2YyODNmZDViNjdhOGNiODk2OTQ0ZDFiZTI4YzdiMzIxMTdjZmM3NTdjODFlOTYifSwic2NvcGVzIjpbImlkc19jb25uZWN0b3IiXSwiYXVkIjoiSURTX0Nvbm5lY3RvciIsImlzcyI6Imh0dHBzOi8vZGFwcy5haXNlYy5mcmF1bmhvZmVyLmRlIiwic3ViIjoiQz1ERSxPPUZyYXVuaG9mZXIsT1U9SVNTVCxDTj01ODc3NmViZS1mOGY4LTRhNmYtYjQ0Yi1lZWVmYTQ3ZmMwNGIiLCJuYmYiOjE2MDIxNDQwNzMsImV4cCI6MTYwMjE0NzY3M30.SG1Av3G00ne2tYQMerrJbhg9f24klDMjS5ur1aykIGHrL5AyL2wsLit_5aMhG12DUQ7tPa2o4RHyTCQFAhVKkI9_bwCR9jGBcN6jfVn8vjxQ3mDvNdWOoRURI_3YOAjBlo1TqFLOKBmN3uTsB_ns7LqJDruea07sme5O38NOukHPWxsAnoiH4N9NByxHqxayrFj0buDxJCLKXG3_FQtZBcsGO89geylFec0epehh9pL5QV5nr4xLzVhfrJRgx512KVqr1hNLqfNRWGl0TFoKHyEE5J8IMEihZwF76_4kl_1HZe1HP866yO8ceONfTvRI2sCXmKpP8A02NGhisEF_Mg\",\r\n" + - " \"ids:tokenFormat\" : {\r\n" + - " \"@id\" : \"idsc:JWT\"\r\n" + - " }\r\n" + - " },\r\n" + - " \"ids:senderAgent\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:correlationMessage\" : {\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/descriptionRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - " }\r\n" + - "}\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"payload\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 7004\r\n" + - "\r\n" + - "{\r\n" + - " \"@context\" : {\r\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\",\r\n" + - " \"idsc\" : \"https://w3id.org/idsa/code/\"\r\n" + - " },\r\n" + - " \"@type\" : \"ids:BaseConnector\",\r\n" + - " \"@id\" : \"58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\",\r\n" + - " \"ids:version\" : \"Wed Oct 07 08:22:50 GMT 2020\",\r\n" + - " \"ids:publicKey\" : {\r\n" + - " \"@type\" : \"ids:PublicKey\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/publicKey/11d7c39d-47fd-4888-8996-6481b405324e\",\r\n" + - " \"ids:keyType\" : {\r\n" + - " \"@id\" : \"idsc:RSA\"\r\n" + - " },\r\n" + - " \"ids:keyValue\" : \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuw6mFrdflXZTJgFOA5smDXC09SmpJWoGpyERZNEy31pKdsRGhTipR27j9irmmqihv7gIgzCnx6kIRNGI2u0oFQ5FgvO1xxgzcihdpF0CheOf9INgisPkq5hj8Ae/DYXkvjhQ6c6ak/ZYfj0NpqyEPcJ5MLRmYGexMaMZmTbqDJvJl5JG3+bE3Ya21hTZYOxiSicpfFgJ30kn5aUIAtd05IZy7z1sDiVLtTXlLfe/ZQC4pnjFts+tc12sX9ihImnCkd0Wvz3CTZoyBSsc1TdBkb9m0C5tvg0fQP4QgF/zH2QoZnnrI52uAZ8MomWtY2lt3D0kkpR69pfVDJ7y3vN/ewIDAQAB\"\r\n" + - " },\r\n" + - " \"ids:description\" : [ {\r\n" + - " \"@value\" : \"IDS Connector with static example resources\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:securityProfile\" : {\r\n" + - " \"@id\" : \"idsc:BASE_CONNECTOR_SECURITY_PROFILE\"\r\n" + - " },\r\n" + - " \"ids:curator\" : {\r\n" + - " \"@id\" : \"https://www.isst.fraunhofer.de/\"\r\n" + - " },\r\n" + - " \"ids:maintainer\" : {\r\n" + - " \"@id\" : \"https://www.isst.fraunhofer.de/\"\r\n" + - " },\r\n" + - " \"ids:catalog\" : {\r\n" + - " \"@type\" : \"ids:Catalog\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/catalog/7b66495a-f6ab-4b55-aa5b-2c77108f6614\",\r\n" + - " \"ids:offer\" : [ {\r\n" + - " \"@type\" : \"ids:DataResource\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/dataResource/7434f738-87f8-45c7-adad-14fdb09bc931\",\r\n" + - " \"ids:language\" : [ {\r\n" + - " \"@id\" : \"idsc:EN\"\r\n" + - " } ],\r\n" + - " \"ids:resourceEndpoint\" : [ {\r\n" + - " \"@type\" : \"ids:StaticEndpoint\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/staticEndpoint/981f5e8d-4373-4579-8d48-13bcdff9e113\",\r\n" + - " \"ids:path\" : \"resources/7434f738-87f8-45c7-adad-14fdb09bc931\",\r\n" + - " \"ids:endpointArtifact\" : {\r\n" + - " \"@type\" : \"ids:Artifact\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifact/fd51c117-1e2c-4887-85c2-fdf446574da4\",\r\n" + - " \"ids:fileName\" : \"String Resource\",\r\n" + - " \"ids:creationDate\" : {\r\n" + - " \"@value\" : \"2020-08-07T05:45:26.604Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:byteSize\" : 12\r\n" + - " },\r\n" + - " \"ids:endpointHost\" : {\r\n" + - " \"@type\" : \"ids:Host\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/host/c76a6e73-96bc-4d14-9a06-0386fef1f040\",\r\n" + - " \"ids:protocol\" : {\r\n" + - " \"@id\" : \"idsc:HTTP2\"\r\n" + - " },\r\n" + - " \"ids:accessUrl\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " }\r\n" + - " }\r\n" + - " } ],\r\n" + - " \"ids:keyword\" : [ {\r\n" + - " \"@value\" : \"sample\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " }, {\r\n" + - " \"@value\" : \"data\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " }, {\r\n" + - " \"@value\" : \"string\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:representation\" : [ {\r\n" + - " \"@type\" : \"ids:Representation\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/representation/21c00901-c804-4624-9024-007beada1fc3\",\r\n" + - " \"ids:instance\" : [ {\r\n" + - " \"@type\" : \"ids:Artifact\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifact/fd51c117-1e2c-4887-85c2-fdf446574da4\",\r\n" + - " \"ids:fileName\" : \"String Resource\",\r\n" + - " \"ids:creationDate\" : {\r\n" + - " \"@value\" : \"2020-08-07T05:45:26.604Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:byteSize\" : 12\r\n" + - " } ],\r\n" + - " \"ids:mediaType\" : {\r\n" + - " \"@type\" : \"ids:IANAMediaType\",\r\n" + - " \"@id\" : \"idsc:TEXT_PLAIN\"\r\n" + - " }\r\n" + - " } ],\r\n" + - " \"ids:description\" : [ {\r\n" + - " \"@value\" : \"This is a sample data resource as string.\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:modified\" : {\r\n" + - " \"@value\" : \"2020-08-13T06:20:57.442Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:title\" : [ {\r\n" + - " \"@value\" : \"String Resource\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ]\r\n" + - " }, {\r\n" + - " \"@type\" : \"ids:DataResource\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/dataResource/bffed7f9-2305-4fca-89d6-e3dad6c6c1a7\",\r\n" + - " \"ids:language\" : [ {\r\n" + - " \"@id\" : \"idsc:EN\"\r\n" + - " } ],\r\n" + - " \"ids:resourceEndpoint\" : [ {\r\n" + - " \"@type\" : \"ids:StaticEndpoint\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/staticEndpoint/0b9e1da7-f050-4ad7-9adc-37ea8a7676dc\",\r\n" + - " \"ids:path\" : \"resources/bffed7f9-2305-4fca-89d6-e3dad6c6c1a7\",\r\n" + - " \"ids:endpointArtifact\" : {\r\n" + - " \"@type\" : \"ids:Artifact\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifact/25998c93-71b7-45ad-a02b-f410ca324f0d\",\r\n" + - " \"ids:fileName\" : \"File Resource\",\r\n" + - " \"ids:creationDate\" : {\r\n" + - " \"@value\" : \"2020-08-07T06:21:46.614Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:byteSize\" : 6032\r\n" + - " },\r\n" + - " \"ids:endpointHost\" : {\r\n" + - " \"@type\" : \"ids:Host\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/host/1d166045-f81e-481b-b327-e9e95085f9fd\",\r\n" + - " \"ids:protocol\" : {\r\n" + - " \"@id\" : \"idsc:HTTP2\"\r\n" + - " },\r\n" + - " \"ids:accessUrl\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " }\r\n" + - " }\r\n" + - " } ],\r\n" + - " \"ids:keyword\" : [ {\r\n" + - " \"@value\" : \"sample\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " }, {\r\n" + - " \"@value\" : \"data\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " }, {\r\n" + - " \"@value\" : \"image\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:representation\" : [ {\r\n" + - " \"@type\" : \"ids:Representation\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/representation/11ca84ad-204c-4d0a-a1fa-a1b53f2e3f4c\",\r\n" + - " \"ids:instance\" : [ {\r\n" + - " \"@type\" : \"ids:Artifact\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/artifact/25998c93-71b7-45ad-a02b-f410ca324f0d\",\r\n" + - " \"ids:fileName\" : \"File Resource\",\r\n" + - " \"ids:creationDate\" : {\r\n" + - " \"@value\" : \"2020-08-07T06:21:46.614Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:byteSize\" : 6032\r\n" + - " } ],\r\n" + - " \"ids:mediaType\" : {\r\n" + - " \"@type\" : \"ids:IANAMediaType\",\r\n" + - " \"@id\" : \"idsc:IMAGE_PNG\"\r\n" + - " }\r\n" + - " } ],\r\n" + - " \"ids:description\" : [ {\r\n" + - " \"@value\" : \"This is a sample data resource as base64 encoded file.\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:modified\" : {\r\n" + - " \"@value\" : \"2020-08-07T06:22:29.128Z\",\r\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:title\" : [ {\r\n" + - " \"@value\" : \"File Resource\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ]\r\n" + - " } ]\r\n" + - " },\r\n" + - " \"ids:outboundModelVersion\" : \"3.1.0\",\r\n" + - " \"ids:defaultHost\" : {\r\n" + - " \"@type\" : \"ids:Host\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/host/057fdf0b-b416-4242-8265-53e46dc16ee6\",\r\n" + - " \"ids:protocol\" : {\r\n" + - " \"@id\" : \"idsc:HTTP2\"\r\n" + - " },\r\n" + - " \"ids:accessUrl\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " }\r\n" + - " },\r\n" + - " \"ids:inboundModelVersion\" : [ \"3.1.0\" ],\r\n" + - " \"ids:title\" : [ {\r\n" + - " \"@value\" : \"Dataspace Connector hosted by the Fraunhofer ISST\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ]\r\n" + - "}\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh--"; - } - - private String getArtifactDescriptionMultipart() { - return "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"header\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 2119\r\n" + - "\r\n" + - "{\r\n" + - " \"@context\" : {\r\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\",\r\n" + - " \"idsc\" : \"https://w3id.org/idsa/code/\"\r\n" + - " },\r\n" + - " \"@type\" : \"ids:DescriptionResponseMessage\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/descriptionResponseMessage/d4957419-abc9-4529-a534-fc44d3940a19\",\r\n" + - " \"ids:modelVersion\" : \"3.1.0\",\r\n" + - " \"ids:issued\" : {\r\n" + - " \"@value\" : \"2020-10-07T08:23:19.938Z\",\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#dateTimeStamp\"\r\n" + - " },\r\n" + - " \"ids:issuerConnector\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:recipientConnector\" : [ {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/\"\r\n" + - " } ],\r\n" + - " \"ids:securityToken\" : {\r\n" + - " \"@type\" : \"ids:DynamicAttributeToken\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/dynamicAttributeToken/5ab6804f-900a-471c-a1d1-7dee49f90695\",\r\n" + - " \"ids:tokenValue\" : \"eyJ0eXAiOiJKV1QiLCJraWQiOiJkZWZhdWx0IiwiYWxnIjoiUlMyNTYifQ.eyJpZHNfYXR0cmlidXRlcyI6eyJzZWN1cml0eV9wcm9maWxlIjp7ImF1ZGl0X2xvZ2dpbmciOjB9LCJtZW1iZXJzaGlwIjp0cnVlLCJpZHMtdXJpIjoiaHR0cDovL3NvbWUtdXJpIiwidHJhbnNwb3J0X2NlcnRzX3NoYTI1OCI6ImJhY2I4Nzk1NzU3MzBiYjA4M2YyODNmZDViNjdhOGNiODk2OTQ0ZDFiZTI4YzdiMzIxMTdjZmM3NTdjODFlOTYifSwic2NvcGVzIjpbImlkc19jb25uZWN0b3IiXSwiYXVkIjoiSURTX0Nvbm5lY3RvciIsImlzcyI6Imh0dHBzOi8vZGFwcy5haXNlYy5mcmF1bmhvZmVyLmRlIiwic3ViIjoiQz1ERSxPPUZyYXVuaG9mZXIsT1U9SVNTVCxDTj01ODc3NmViZS1mOGY4LTRhNmYtYjQ0Yi1lZWVmYTQ3ZmMwNGIiLCJuYmYiOjE2MDIxNDQwNzMsImV4cCI6MTYwMjE0NzY3M30.SG1Av3G00ne2tYQMerrJbhg9f24klDMjS5ur1aykIGHrL5AyL2wsLit_5aMhG12DUQ7tPa2o4RHyTCQFAhVKkI9_bwCR9jGBcN6jfVn8vjxQ3mDvNdWOoRURI_3YOAjBlo1TqFLOKBmN3uTsB_ns7LqJDruea07sme5O38NOukHPWxsAnoiH4N9NByxHqxayrFj0buDxJCLKXG3_FQtZBcsGO89geylFec0epehh9pL5QV5nr4xLzVhfrJRgx512KVqr1hNLqfNRWGl0TFoKHyEE5J8IMEihZwF76_4kl_1HZe1HP866yO8ceONfTvRI2sCXmKpP8A02NGhisEF_Mg\",\r\n" + - " \"ids:tokenFormat\" : {\r\n" + - " \"@id\" : \"idsc:JWT\"\r\n" + - " }\r\n" + - " },\r\n" + - " \"ids:senderAgent\" : {\r\n" + - " \"@id\" : \"https://simpleconnector.ids.isst.fraunhofer.de/58776ebe-f8f8-4a6f-b44b-eeefa47fc04b\"\r\n" + - " },\r\n" + - " \"ids:correlationMessage\" : {\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/descriptionRequestMessage/3d621945-9839-4a2e-8437-4db7d1f959ca\"\r\n" + - " }\r\n" + - "}\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh\r\n" + - "Content-Disposition: form-data; name=\"payload\"\r\n" + - "Content-Type: text/plain;charset=UTF-8\r\n" + - "Content-Length: 822\r\n" + - "\r\n" + - "{\r\n" + - " \"@context\" : {\r\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\",\r\n" + - " \"idsc\" : \"https://w3id.org/idsa/code/\"\r\n" + - " },\r\n" + - " \"@type\" : \"ids:Resource\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/resource/4198281f-1c79-4c87-8584-48262432cdc2\",\r\n" + - " \"ids:contractOffer\": {\n" + - " \"@context\" : {\n" + - " \"ids\" : \"https://w3id.org/idsa/core/\"\n" + - " },\n" + - " \"@type\" : \"ids:ContractOffer\",\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/contractOffer/110e659b-d171-4519-8a65-8a2c297ec296\",\n" + - " \"ids:permission\" : [ {\n" + - " \"@type\" : \"ids:Permission\",\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/permission/7e1a166d-8c42-492f-afb8-204cea7aacf6\",\n" + - " \"ids:description\" : [ {\n" + - " \"@value\" : \"provide-access\",\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + - " } ],\n" + - " \"ids:action\" : [ {\n" + - " \"@id\" : \"idsc:USE\"\n" + - " } ],\n" + - " \"ids:title\" : [ {\n" + - " \"@value\" : \"Example Usage Policy\",\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + - " } ]\n" + - " } ]\n" + - " }," + - " \"ids:language\" : [ {\r\n" + - " \"@id\" : \"idsc:EN\"\r\n" + - " } ],\r\n" + - " \"ids:version\" : \"v1.0\",\r\n" + - " \"ids:description\" : [ {\r\n" + - " \"@value\" : \"\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:title\" : [ {\r\n" + - " \"@value\" : \"Test resource\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:representation\" : [ ],\r\n" + - " \"ids:standardLicense\" : {\r\n" + - " \"@id\" : \"http://license.com\"\r\n" + - " },\r\n" + - " \"ids:keyword\" : [ {\r\n" + - " \"@value\" : \"test\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " }, {\r\n" + - " \"@value\" : \"resource\",\r\n" + - " \"@language\" : \"en\"\r\n" + - " } ],\r\n" + - " \"ids:publisher\" : {\r\n" + - " \"@id\" : \"http://resource-owner.com\"\r\n" + - " },\r\n" + - " \"ids:resourceEndpoint\" : [ {\r\n" + - " \"@type\" : \"ids:ConnectorEndpoint\",\r\n" + - " \"@id\" : \"https://w3id.org/idsa/autogen/connectorEndpoint/e5e2ab04-633a-44b9-87d9-a097ae6da3cf\",\r\n" + - " \"ids:accessURL\" : {\r\n" + - " \"@id\" : \"/api/ids/data\"\r\n" + - " }\r\n" + - " } ],\r\n" + - " \"ids:policy\" : \"{\\r\\n \\\"@context\\\" : null,\\r\\n \\\"@type\\\" : null,\\r\\n \\\"uid\\\" : null,\\r\\n \\\"obligation\\\" : null,\\r\\n \\\"permission\\\" : null,\\r\\n \\\"prohibition\\\" : null\\r\\n}\"\r\n" + - "}\r\n" + - "--6-68GNd1LWhpTA8tVYaMkSDhNKSL67_C_NYQSh--\r\n"; - } - - -} diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/SelfDescriptionTest.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/SelfDescriptionTest.java deleted file mode 100644 index c77b5c63a..000000000 --- a/src/test/java/de/fraunhofer/isst/dataspaceconnector/integrationtest/SelfDescriptionTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.fraunhofer.isst.dataspaceconnector.integrationtest; - -import de.fraunhofer.iais.eis.Connector; -import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.iais.eis.ids.jsonld.Serializer; -import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; -import de.fraunhofer.isst.dataspaceconnector.services.resource.*; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -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.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import javax.transaction.Transactional; -import java.lang.reflect.Field; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.UUID; - -/** - * This class tests whether the connecter can give a valid selfdescription. - * - * @author Ronja Quensel - * @version $Id: $Id - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class SelfDescriptionTest { - private final String selfDescriptionEndpoint = "/admin/api/selfservice"; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private OfferedResourceService offeredResourceService; - - @Autowired - private RequestedResourceService requestedResourceService; - - @Autowired - private OfferedResourceRepository offeredResourceRepository; - - @Autowired - private RequestedResourceRepository requestedResourceRepository; - - @Autowired - private Serializer serializer; - - @Test - @Transactional - @WithMockUser(roles = {"ADMIN"}) - public void getSelfDescription_noResources() throws Exception { - deleteAllResources(); - - String response = mockMvc.perform(MockMvcRequestBuilders.get(selfDescriptionEndpoint)) - .andReturn().getResponse().getContentAsString(); - - Connector connector = serializer.deserialize(response, Connector.class); - - Assert.assertTrue(connector.getResourceCatalog().get(0).getOfferedResource().isEmpty()); - Assert.assertTrue(connector.getResourceCatalog().get(0).getRequestedResource().isEmpty()); - } - - @Test - @Transactional - @WithMockUser(roles = {"ADMIN"}) - public void getSelfDescription_withResources() throws Exception { - deleteAllResources(); - - UUID offeredResourceId = offeredResourceService.addResource(getResourceMetadata()); - offeredResourceService.addData(offeredResourceId, "Hi, I'm data!"); - - UUID requestedResourceId = requestedResourceService.addResource(getResourceMetadata()); - requestedResourceService.addData(requestedResourceId, "I'm also data!"); - - String response = mockMvc.perform(MockMvcRequestBuilders.get(selfDescriptionEndpoint)) - .andReturn().getResponse().getContentAsString(); - - Connector connector = serializer.deserialize(response, Connector.class); - Assert.assertEquals(1, connector.getResourceCatalog().get(0).getOfferedResource().size()); - Assert.assertEquals(1, connector.getResourceCatalog().get(0).getRequestedResource().size()); - } - - private ResourceMetadata getResourceMetadata() { - return new ResourceMetadata("Test resource", "", Arrays.asList("test", "resource"), "policy", - URI.create("http://resource-owner.com"), URI.create("http://license.com"), "v1.0", - new ArrayList<>()); - } - - private void deleteAllResources() throws Exception { - offeredResourceRepository.deleteAll(); - requestedResourceRepository.deleteAll(); - - Field offeredResourcesMapField = OfferedResourceServiceImpl.class.getDeclaredField("offeredResources"); - offeredResourcesMapField.setAccessible(true); - offeredResourcesMapField.set(offeredResourceService, new HashMap()); - - Field requestedResourcesMapField = RequestedResourceServiceImpl.class.getDeclaredField("requestedResources"); - requestedResourcesMapField.setAccessible(true); - requestedResourcesMapField.set(requestedResourceService, new HashMap()); - } - -} diff --git a/src/test/java/io/dataspaceconnector/ConnectorApplicationIT.java b/src/test/java/io/dataspaceconnector/ConnectorApplicationIT.java new file mode 100644 index 000000000..a11d3f85f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/ConnectorApplicationIT.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ConnectorApplicationIT { + @Test + void contextLoads() { + } +} diff --git a/src/test/java/io/dataspaceconnector/OpenApiIT.java b/src/test/java/io/dataspaceconnector/OpenApiIT.java new file mode 100644 index 000000000..1374094dd --- /dev/null +++ b/src/test/java/io/dataspaceconnector/OpenApiIT.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//package io.dataspaceconnector; +// +//import java.nio.file.Files; +//import java.nio.file.Path; +// +//import org.junit.jupiter.api.Test; +//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.security.test.context.support.WithMockUser; +//import org.springframework.test.web.servlet.MockMvc; +// +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +//@SpringBootTest +//@AutoConfigureMockMvc +//public class OpenApiIT { +// +// @Autowired +// MockMvc mockMvc; +// +// @Test +// @WithMockUser("ADMIN") +// public void compare_openApi_equals() throws Exception { +// /* ASSERT */ +// final var result = mockMvc.perform(get("/v3/api-docs.yaml")).andExpect(status().isOk()).andReturn(); +// +// /* ASSERT */ +// final var currentOpenApi = Files.readString(Path.of("openapi.yaml")); +// if(!currentOpenApi.equals(result.getResponse().getContentAsString())) { +// Files.writeString(Path.of("openapi-new.yaml"), result.getResponse().getContentAsString()); +// } +// +// assertEquals(currentOpenApi, result.getResponse().getContentAsString()); +// } +//} diff --git a/src/test/java/io/dataspaceconnector/config/UsageControlFrameworkTest.java b/src/test/java/io/dataspaceconnector/config/UsageControlFrameworkTest.java new file mode 100644 index 000000000..ef1a69409 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/config/UsageControlFrameworkTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.config; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UsageControlFrameworkTest { + + @Test + public void toString_nothing_returnValidOutput() { + /* ARRANGE */ + final var input = UsageControlFramework.INTERNAL; + + /* ACT */ + final var inputAsString = input.toString(); + + /* ASSERT */ + assertEquals("INTERNAL", inputAsString); + } +} diff --git a/src/test/java/io/dataspaceconnector/configuration/DatabaseTestsConfig.java b/src/test/java/io/dataspaceconnector/configuration/DatabaseTestsConfig.java new file mode 100644 index 000000000..2879839eb --- /dev/null +++ b/src/test/java/io/dataspaceconnector/configuration/DatabaseTestsConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.configuration; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableAutoConfiguration +@EnableJpaRepositories(basePackages = {"io.dataspaceconnector.repositories"}) +@EntityScan("io.dataspaceconnector.model") +@ComponentScan(basePackages = {"io.dataspaceconnector.repositories", + "io.dataspaceconnector.model"}, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE)}) +public class DatabaseTestsConfig { +} diff --git a/src/test/java/io/dataspaceconnector/controller/ConfigurationControllerTest.java b/src/test/java/io/dataspaceconnector/controller/ConfigurationControllerTest.java new file mode 100644 index 000000000..4fb8c2442 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/ConfigurationControllerTest.java @@ -0,0 +1,537 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import de.fraunhofer.iais.eis.ConfigurationModelBuilder; +import de.fraunhofer.iais.eis.ConnectorDeployMode; +import de.fraunhofer.iais.eis.ConnectorStatus; +import de.fraunhofer.iais.eis.LogLevel; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.dataspaceconnector.config.ConnectorConfiguration; +import io.dataspaceconnector.services.ids.DeserializationService; +import net.minidev.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@AutoConfigureMockMvc +@SpringBootTest +public class ConfigurationControllerTest { + + @MockBean + private ConfigurationContainer configContainer; + + @MockBean + private ConnectorConfiguration connectorConfig; + + @MockBean + private DeserializationService idsService; + + @Autowired + MockMvc mockMvc; + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_validJson_consumesJson() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + Mockito.when(configContainer.getConfigModel()).thenReturn(model); + + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_validJsonLd_consumesJsonLd() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + Mockito.when(configContainer.getConfigModel()).thenReturn(model); + + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration") + .contentType("application/ld+json") + .content(model.toRdf())) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_validJson_producesJsonLd() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + Mockito.when(configContainer.getConfigModel()).thenReturn(model); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/ld+json", result.getResponse().getContentType()); + } + + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_invalidMediaType_return415() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_ATOM_XML) + .content(model.toRdf())) + .andExpect(status().is4xxClientError()).andReturn(); + + assertEquals(415, result.getResponse().getStatus()); + } + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_validJson_200AndReturnsNewConfig() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + Mockito.when(configContainer.getConfigModel()).thenReturn(model); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isOk()).andReturn(); + + Mockito.verify(configContainer, Mockito.atLeastOnce()).updateConfiguration(Mockito.any()); + + assertEquals(model.toRdf(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_invalidJson_return400() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenThrow(IllegalArgumentException.class); + + + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser("ADMIN") + public void updateConfiguration_validJsonFailsUpdate_return500() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.when(idsService.getConfigurationModel(Mockito.eq(model.toRdf()))).thenReturn(model); + Mockito.doThrow(ConfigurationUpdateException.class).when(configContainer).updateConfiguration(Mockito.eq(model)); + + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isInternalServerError()); + } + + @Test + public void updateConfiguration_unauthorized_return500() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration") + .contentType(MediaType.APPLICATION_JSON) + .content(model.toRdf())) + .andExpect(status().isUnauthorized()); + + + Mockito.verify(configContainer, Mockito.never()).updateConfiguration(Mockito.any()); + } + + /** + * getConfiguration + */ + + @Test + public void getConfiguration_unauthorized_return500() throws Exception { + /* ACT && ASSERT */ + mockMvc.perform(get("/api/configuration")) + .andExpect(status().isUnauthorized()); + } + + + @Test + @WithMockUser("ADMIN") + public void getConfiguration_hasConfig_returnConfig() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.doReturn(model).when(configContainer).getConfigModel(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration")) + .andExpect(status().isOk()).andReturn(); + + assertEquals(model.toRdf(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void getConfiguration_hasConfig_returnMediaTypeJson() throws Exception { + /* ARRANGE */ + final var model = new ConfigurationModelBuilder() + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._configurationModelLogLevel_(LogLevel.MINIMAL_LOGGING) + ._connectorStatus_(ConnectorStatus.CONNECTOR_OFFLINE) + .build(); + + Mockito.doReturn(model).when(configContainer).getConfigModel(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration")) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/json", result.getResponse().getContentType()); + } + + @Test + @WithMockUser("ADMIN") + public void getConfiguration_noConfig_return404() throws Exception { + /* ARRANGE */ + Mockito.doReturn(null).when(configContainer).getConfigModel(); + + /* ACT && ASSERT */ + mockMvc.perform(get("/api/configuration")) + .andExpect(status().isNotFound()); + } + + /** + * setNegotiationStatus + */ + + @Test + public void setNegotiationStatus_unauthorized_return500() throws Exception { + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration/negotiation") + .param("status", "true")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void setNegotiationStatus_noStatusParam_return401() throws Exception { + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/negotiation")) + .andExpect(status().is4xxClientError()).andReturn(); + + assertEquals(400, result.getResponse().getStatus()); + } + + @Test + @WithMockUser("ADMIN") + public void setNegotiationStatus_true_returnTrue() throws Exception { + final var body = new JSONObject(); + body.put("status", true); + + Mockito.doReturn(true).when(connectorConfig).isPolicyNegotiation(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/negotiation") + .param("status", "true")) + .andExpect(status().isOk()).andReturn(); + + Mockito.verify(connectorConfig, Mockito.atLeastOnce()).setPolicyNegotiation(Mockito.eq(true)); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void setNegotiationStatus_false_returnFalse() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isPolicyNegotiation(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/negotiation") + .param("status", "false")) + .andExpect(status().isOk()).andReturn(); + + Mockito.verify(connectorConfig, Mockito.atLeastOnce()).setPolicyNegotiation(Mockito.eq(false)); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void setNegotiationStatus_any_returnJson() throws Exception { + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/negotiation") + .param("status", "false")) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/json", result.getResponse().getContentType()); + } + + + /** + * getNegotiationStatus + */ + + @Test + public void getNegotiationStatus_unauthorized_return500() throws Exception { + /* ACT && ASSERT */ + mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void getNegotiationStatus_isTrue_returnTrue() throws Exception { + final var body = new JSONObject(); + body.put("status", true); + + Mockito.doReturn(true).when(connectorConfig).isPolicyNegotiation(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isOk()).andReturn(); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void getNegotiationStatus_isFalse_returnFalse() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isPolicyNegotiation(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isOk()).andReturn(); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void getNegotiationStatus_any_returnJson() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isPolicyNegotiation(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/json", result.getResponse().getContentType()); + } + + /** + * setPatternStatus + */ + + @Test + public void setPatternStatus_unauthorized_return500() throws Exception { + /* ACT && ASSERT */ + mockMvc.perform(put("/api/configuration/pattern") + .param("status", "true")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void setPatternStatus_noStatusParam_return401() throws Exception { + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/pattern")) + .andExpect(status().is4xxClientError()).andReturn(); + + assertEquals(400, result.getResponse().getStatus()); + } + + @Test + @WithMockUser("ADMIN") + public void setPatternStatus_true_returnTrue() throws Exception { + final var body = new JSONObject(); + body.put("status", true); + + Mockito.doReturn(true).when(connectorConfig).isAllowUnsupported(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/pattern") + .param("status", "true")) + .andExpect(status().isOk()).andReturn(); + + Mockito.verify(connectorConfig, Mockito.atLeastOnce()).setAllowUnsupported(Mockito.eq(true)); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void setPatternStatus_false_returnFalse() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isAllowUnsupported(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/pattern") + .param("status", "false")) + .andExpect(status().isOk()).andReturn(); + + Mockito.verify(connectorConfig, Mockito.atLeastOnce()).setAllowUnsupported(Mockito.eq(false)); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void setPatternStatus_any_returnJson() throws Exception { + /* ACT && ASSERT */ + final var result = mockMvc.perform(put("/api/configuration/pattern") + .param("status", "false")) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/json", result.getResponse().getContentType()); + } + + /** + * getPatternStatus + */ + + @Test + public void getPatternStatus_unauthorized_return500() throws Exception { + /* ACT && ASSERT */ + mockMvc.perform(get("/api/configuration/pattern")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void getPatternStatus_isTrue_returnTrue() throws Exception { + final var body = new JSONObject(); + body.put("status", true); + + Mockito.doReturn(true).when(connectorConfig).isAllowUnsupported(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/pattern")) + .andExpect(status().isOk()).andReturn(); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void getPatternStatus_isFalse_returnFalse() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isAllowUnsupported(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isOk()).andReturn(); + + assertEquals(body.toJSONString(), result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void getPatternStatus_any_returnJson() throws Exception { + final var body = new JSONObject(); + body.put("status", false); + + Mockito.doReturn(false).when(connectorConfig).isAllowUnsupported(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/configuration/negotiation")) + .andExpect(status().isOk()).andReturn(); + + assertEquals("application/json", result.getResponse().getContentType()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/ExampleControllerTest.java b/src/test/java/io/dataspaceconnector/controller/ExampleControllerTest.java new file mode 100644 index 000000000..19aa98dc6 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/ExampleControllerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; + +import de.fraunhofer.iais.eis.BaseConnectorBuilder; +import de.fraunhofer.iais.eis.BasicAuthenticationBuilder; +import de.fraunhofer.iais.eis.ConfigurationModelBuilder; +import de.fraunhofer.iais.eis.ConnectorDeployMode; +import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +import de.fraunhofer.iais.eis.ConnectorStatus; +import de.fraunhofer.iais.eis.KeyType; +import de.fraunhofer.iais.eis.LogLevel; +import de.fraunhofer.iais.eis.ProxyBuilder; +import de.fraunhofer.iais.eis.PublicKeyBuilder; +import de.fraunhofer.iais.eis.SecurityProfile; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.services.ids.DeserializationService; +import org.junit.jupiter.api.Test; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ExampleControllerTest { + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private MockMvc mockMvc; + + @Test + public void getConnectorConfiguration_notAuthorized_notAuthorized() throws Exception { + mockMvc.perform(get("/api/examples/configuration")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void getConnectorConfiguration_nothing_sampleConfig() throws Exception { + /* ARRANGE */ + final var expect = new ConfigurationModelBuilder(URI.create("configId")) + ._configurationModelLogLevel_(LogLevel.NO_LOGGING) + ._connectorDeployMode_(ConnectorDeployMode.TEST_DEPLOYMENT) + ._connectorProxy_(Util.asList( + new ProxyBuilder(URI.create("proxiId")) + ._noProxy_(new ArrayList<>(Collections.singletonList( + URI.create("https://localhost:8080/")))) + ._proxyAuthentication_( + new BasicAuthenticationBuilder(URI.create("basicAuthId")).build()) + ._proxyURI_(URI.create( + "proxy.dortmund.isst.fraunhofer.de:3128")) + .build())) + ._connectorStatus_(ConnectorStatus.CONNECTOR_ONLINE) + ._connectorDescription_( + new BaseConnectorBuilder(URI.create("connectorId")) + ._maintainer_(URI.create("https://example.com")) + ._curator_(URI.create("https://example.com")) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + ._outboundModelVersion_("4.0.0") + ._inboundModelVersion_(Util.asList("4.0.0")) + ._title_(Util.asList( + new TypedLiteral("Dataspace Connector"))) + ._description_(Util.asList(new TypedLiteral( + "IDS Connector with static " + + "example resources hosted by the Fraunhofer ISST."))) + ._version_("v3.0.0") + ._publicKey_( + new PublicKeyBuilder(URI.create("keyId")) + ._keyType_(KeyType.RSA) + ._keyValue_( + "Your daps token here.".getBytes( + StandardCharsets.UTF_8)) + .build()) + ._hasDefaultEndpoint_( + new ConnectorEndpointBuilder(URI.create("endpointId")) + ._accessURL_(URI.create("/api/ids/data")) + .build()) + .build()) + ._keyStore_(URI.create("file:///conf/keystore.p12")) + ._trustStore_(URI.create("file:///conf/truststore.p12")) + .build(); + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api/examples/configuration")).andExpect(status().isOk()).andReturn(); + assertEquals(expect.toRdf(), result.getResponse().getContentAsString()); + } + + /** + * getPolicyPattern + */ + + @Test + public void getPolicyPattern_notAuthorized_notAuthorized() throws Exception { + mockMvc.perform(get("/api/examples/validation")).andExpect(status().isUnauthorized()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/GlobalExceptionHandlerTest.java b/src/test/java/io/dataspaceconnector/controller/GlobalExceptionHandlerTest.java new file mode 100644 index 000000000..af14aec88 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/GlobalExceptionHandlerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import net.minidev.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GlobalExceptionHandlerTest { + + private GlobalExceptionHandler handler = new GlobalExceptionHandler(); + + @Test + public void handleAnyException_anyException_returnInternalServerError() { + /* ARRANGE */ + final var exception = new RuntimeException("Some problem"); + + /* ACT */ + final var result = handler.handleAnyException(exception); + + /* ASSERT */ + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } + + @Test + public void handleAnyException_anyException_returnXErrorHeader() { + /* ARRANGE */ + final var exception = new RuntimeException("Some problem"); + + /* ACT */ + final var result = handler.handleAnyException(exception); + + /* ASSERT */ + assertTrue(result.getHeaders().containsKey("X-Error")); + assertEquals("true", result.getHeaders().get("X-Error").get(0)); + } + + @Test + public void handleAnyException_anyException_returnJsonContentType() { + /* ARRANGE */ + final var exception = new RuntimeException("Some problem"); + + /* ACT */ + final var result = handler.handleAnyException(exception); + + /* ASSERT */ + assertEquals(MediaType.APPLICATION_JSON, result.getHeaders().getContentType()); + } + + @Test + public void handleAnyException_anyException_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "An error occurred. Please try again later."); + + final var exception = new RuntimeException("Some problem"); + + /* ACT */ + final var result = handler.handleAnyException(exception); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } + + @Test + public void handleAnyException_null_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "An error occurred. Please try again later."); + + /* ACT */ + final var result = handler.handleAnyException(null); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandlerTest.java b/src/test/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandlerTest.java new file mode 100644 index 000000000..96115b774 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/JsonProcessingExceptionHandlerTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import net.minidev.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JsonProcessingExceptionHandlerTest { + + private JsonProcessingExceptionHandler handler = new JsonProcessingExceptionHandler(); + + + @Test + public void handleJsonProcessingException_anyException_returnBadRequest() { + /* ARRANGE */ + final var exception = new JsonProcessingException("Some problem"){}; + + /* ACT */ + final var result = handler.handleJsonProcessingException(exception); + + /* ASSERT */ + assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); + } + + @Test + public void handleJsonProcessingException_anyException_returnJsonContentType() { + /* ARRANGE */ + final var exception = new JsonProcessingException("Some problem"){}; + + /* ACT */ + final var result = handler.handleJsonProcessingException(exception); + + /* ASSERT */ + assertEquals(MediaType.APPLICATION_JSON, result.getHeaders().getContentType()); + } + + @Test + public void handleJsonProcessingException_anyException_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "Invalid input."); + + final var exception = new JsonProcessingException("Some problem"){}; + + /* ACT */ + final var result = handler.handleJsonProcessingException(exception); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } + + @Test + public void handleJsonProcessingException_null_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "Invalid input."); + + /* ACT */ + final var result = handler.handleJsonProcessingException(null); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/MainControllerIT.java b/src/test/java/io/dataspaceconnector/controller/MainControllerIT.java new file mode 100644 index 000000000..b9170d210 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/MainControllerIT.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import java.net.URI; + +import de.fraunhofer.iais.eis.BaseConnector; +import de.fraunhofer.iais.eis.BaseConnectorBuilder; +import de.fraunhofer.iais.eis.SecurityProfile; +import de.fraunhofer.iais.eis.ids.jsonld.Serializer; +import io.dataspaceconnector.services.ids.ConnectorService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class MainControllerIT { + + @MockBean + ConnectorService connectorService; + + @Autowired + MockMvc mockMvc; + + @Test + public void getPublicSelfDescription_nothing_returnValidDescription() throws Exception { + /* ARRANGE */ + final var connector = getConnectorWithoutResources(); + Mockito.when(connectorService.getConnectorWithoutResources()).thenReturn(connector); + + /* ACT */ + final var result = mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertDoesNotThrow( () -> new Serializer().deserialize(result.getResponse().getContentAsString(), BaseConnector.class)); + assertEquals(connector.toRdf(), result.getResponse().getContentAsString()); + } + + private BaseConnector getConnectorWithoutResources() { + return new BaseConnectorBuilder() + ._curator_(URI.create("https://someBody")) + ._maintainer_(URI.create("https:://someoneElse")) + ._outboundModelVersion_("4.0.0") + ._inboundModelVersion_(de.fraunhofer.iais.eis.util.Util.asList("4.0.0")) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + .build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/MainControllerTest.java b/src/test/java/io/dataspaceconnector/controller/MainControllerTest.java new file mode 100644 index 000000000..d4f76c60e --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/MainControllerTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import javax.validation.ConstraintViolationException; + +import de.fraunhofer.iais.eis.BaseConnectorBuilder; +import de.fraunhofer.iais.eis.SecurityProfile; +import io.dataspaceconnector.services.ids.ConnectorService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@AutoConfigureMockMvc +@SpringBootTest +public class MainControllerTest { + + @MockBean + private ConnectorService connectorService; + + @Autowired + MockMvc mockMvc; + + @Test + public void getPublicSelfDescription_nothing_returnDescriptionWithOutDescription() throws Exception { + /* ARRANGE */ + final var connector = new BaseConnectorBuilder() + ._curator_(URI.create("someCurator")) + ._outboundModelVersion_("9999") + ._maintainer_(URI.create("someMaintainer")) + ._inboundModelVersion_(new ArrayList<>(Arrays.asList("9991", "9992"))) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + .build(); + Mockito.doReturn(connector).when(connectorService).getConnectorWithoutResources(); + + /* ACT */ + final var result = mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals(connector.toRdf(), result.getResponse().getContentAsString()); + } + + @Test + public void getPublicSelfDescription_serviceFails_InternalServerError() throws Exception { + /* ARRANGE */ + Mockito.doThrow(ConstraintViolationException.class).when(connectorService).getConnectorWithoutResources(); + + /* ACT */ + final var result = mockMvc.perform(get("/")).andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("application/json", result.getResponse().getContentType()); + assertEquals("{\"message\":\"An error occurred. Please try again later.\"}", result.getResponse().getContentAsString()); + } + + /** + * getPrivateSelfDescription + */ + + @Test + @WithMockUser("ADMIN") + public void getPrivateSelfDescription_nothing_returnDescription() throws Exception { + /* ARRANGE */ + final var connector = new BaseConnectorBuilder() + ._curator_(URI.create("someCurator")) + ._outboundModelVersion_("9999") + ._maintainer_(URI.create("someMaintainer")) + ._inboundModelVersion_(new ArrayList<>(Arrays.asList("9991", "9992"))) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + .build(); + Mockito.doReturn(connector).when(connectorService).getConnectorWithOfferedResources(); + + /* ACT */ + final var result = mockMvc.perform(get("/api/connector")).andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals(connector.toRdf(), result.getResponse().getContentAsString()); + } + + @Test + public void getPrivateSelfDescription_nothing_accessRestriction() throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + mockMvc.perform(get("/api/connector")).andExpect(status().isUnauthorized()); + } + + + @Test + @WithMockUser("ADMIN") + public void getPrivateSelfDescription_serviceFails_InternalServerError() throws Exception { + /* ARRANGE */ + Mockito.doThrow(ConstraintViolationException.class).when(connectorService).getConnectorWithOfferedResources(); + + /* ACT */ + final var result = mockMvc.perform(get("/api/connector")).andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("application/json", result.getResponse().getContentType()); + assertEquals("{\"message\":\"An error occurred. Please try again later.\"}", result.getResponse().getContentAsString()); + } + + /** + * root + */ + + @Test + public void root_nothing_accessRestriction() throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + mockMvc.perform(get("/api")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void root_nothing_returnApiEntryPoint() throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + final var result = mockMvc.perform(get("/api")).andExpect(status().isOk()).andReturn(); + + assertEquals("{\"_links\":{\"self\":{\"href\":\"http://localhost/api\"}," + + "\"agreements\":{\"href\":\"http://localhost/api/agreements{?page,size}\"," + + "\"templated\":true}," + + "\"artifacts\":{\"href\":\"http://localhost/api/artifacts{?page,size}\"," + + "\"templated\":true},\"catalogs\":{\"href\":\"http://localhost/api" + + "/catalogs{?page,size}\",\"templated\":true}," + + "\"contracts\":{\"href\":\"http://localhost/api/contracts{?page,size}\"," + + "\"templated\":true}," + + "\"offers\":{\"href\":\"http://localhost/api/offers{?page,size}\"," + + "\"templated\":true},\"representations\":{\"href\":\"http://localhost/api" + + "/representations{?page,size}\",\"templated\":true}," + + "\"requests\":{\"href\":\"http://localhost/api/requests{?page,size}\"," + + "\"templated\":true}," + + "\"rules\":{\"href\":\"http://localhost/api/rules{?page,size}\"," + + "\"templated\":true}}}", result.getResponse().getContentAsString()); + assertEquals("application/hal+json", result.getResponse().getContentType()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandlerTest.java b/src/test/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandlerTest.java new file mode 100644 index 000000000..888d22b34 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/PolicyRestrictionExceptionHandlerTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller; + +import io.dataspaceconnector.exceptions.PolicyRestrictionException; +import io.dataspaceconnector.utils.ErrorMessages; +import net.minidev.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import static org.junit.jupiter.api.Assertions.*; + +public class PolicyRestrictionExceptionHandlerTest { + + private PolicyRestrictionExceptionHandler handler = new PolicyRestrictionExceptionHandler(); + + @Test + public void handlePolicyRestrictionException_anyException_returnForbiddenError() { + /* ARRANGE */ + final var exception = new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + + /* ACT */ + final var result = handler.handlePolicyRestrictionException(exception); + + /* ASSERT */ + assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); + } + + @Test + public void handlePolicyRestrictionException_anyException_returnJsonContentType() { + /* ARRANGE */ + final var exception = new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + + /* ACT */ + final var result = handler.handlePolicyRestrictionException(exception); + + /* ASSERT */ + assertEquals(MediaType.APPLICATION_JSON, result.getHeaders().getContentType()); + } + + @Test + public void handlePolicyRestrictionException_anyException_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "A policy restriction has been detected."); + + final var exception = new PolicyRestrictionException(ErrorMessages.POLICY_RESTRICTION); + + /* ACT */ + final var result = handler.handlePolicyRestrictionException(exception); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } + + @Test + public void handlePolicyRestrictionException_null_returnJsonObject() { + /* ARRANGE */ + final var body = new JSONObject(); + body.put("message", "A policy restriction has been detected."); + + /* ACT */ + final var result = handler.handlePolicyRestrictionException(null); + + /* ASSERT */ + assertEquals(body, result.getBody()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageControllerTest.java b/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageControllerTest.java new file mode 100644 index 000000000..ef8a68a6a --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUnavailableMessageControllerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.dataspaceconnector.services.ids.ConnectorService; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ConnectorUnavailableMessageControllerTest { + + @MockBean + private IDSBrokerService brokerService; + + @MockBean + private ConnectorService connectorService; + + @Autowired + private MockMvc mockMvc; + + private final String recipient = "https://someURL"; + + @Test + public void sendConnectorUpdateMessage_unauthorized_returnUnauthorized() throws Exception { + mockMvc.perform(post("/api/ids/connector/unavailable")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noRecipient_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/unavailable")).andExpect(status().isBadRequest()).andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateConfigModel_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doThrow(ConfigurationUpdateException.class).when(connectorService).updateConfigModel(); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/unavailable") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Failed to update configuration.", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateAtBroker_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doThrow(IOException.class).when(brokerService).unregisterAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/unavailable") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_brokerEmptyResponseBody_throws500() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol(Protocol.HTTP_1_1).code(200).message("").build(); + Mockito.doReturn(response).when(brokerService).unregisterAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/unavailable") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_validRequest_returnsBrokerResponse() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol( + Protocol.HTTP_1_1).code(200).message("") + .body(ResponseBody.create("ANSWER", MediaType + .parse("application/text"))) + .build(); + + Mockito.doReturn(response).when(brokerService).unregisterAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/unavailable") + .param("recipient", "https://someURL")) + .andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals("ANSWER", result.getResponse().getContentAsString()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageControllerTest.java b/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageControllerTest.java new file mode 100644 index 000000000..1cd49c412 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/ConnectorUpdateMessageControllerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import io.dataspaceconnector.services.ids.ConnectorService; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ConnectorUpdateMessageControllerTest { + + @MockBean + private IDSBrokerService brokerService; + + @MockBean + private ConnectorService connectorService; + + @Autowired + private MockMvc mockMvc; + + private final String recipient = "https://someURL"; + + @Test + public void sendConnectorUpdateMessage_unauthorized_returnUnauthorized() throws Exception { + mockMvc.perform(post("/api/ids/connector/update")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noRecipient_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/update")).andExpect(status().isBadRequest()).andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateConfigModel_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doThrow(ConfigurationUpdateException.class).when(connectorService).updateConfigModel(); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/update") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Failed to update configuration.", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateAtBroker_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doThrow(IOException.class).when(brokerService).updateSelfDescriptionAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/update") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_brokerEmptyResponseBody_throws500() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol(Protocol.HTTP_1_1).code(200).message("").build(); + Mockito.doReturn(response).when(brokerService).updateSelfDescriptionAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/update") + .param("recipient", recipient)) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_validRequest_returnsBrokerResponse() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol( + Protocol.HTTP_1_1).code(200).message("") + .body(ResponseBody.create("ANSWER", MediaType + .parse("application/text"))) + .build(); + + Mockito.doReturn(response).when(brokerService).updateSelfDescriptionAtBroker(Mockito.eq(recipient)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/connector/update") + .param("recipient", recipient)) + .andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals("ANSWER", result.getResponse().getContentAsString()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageControllerIT.java b/src/test/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageControllerIT.java new file mode 100644 index 000000000..590a697ee --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/DescriptionRequestMessageControllerIT.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import org.junit.jupiter.api.Test; +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.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class DescriptionRequestMessageControllerIT { + + @Autowired + MockMvc mockMvc; + + @Test + public void sendDescriptionRequestMessage_anything_unauthorized() throws Exception { + mockMvc.perform(post("/api/ids/description")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(roles = {"ADMIN"}) + public void sendDescriptionRequestMessage_validInput_() throws Exception { + /* ARRANGE */ + + /* ACT && ASSERT */ + mockMvc.perform(post("/api/ids/description") + .param("recipient", "https://localhost:8080/") + ).andExpect(status().isOk()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/QueryMessageControllerTest.java b/src/test/java/io/dataspaceconnector/controller/messages/QueryMessageControllerTest.java new file mode 100644 index 000000000..0bf4a700f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/QueryMessageControllerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; + +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class QueryMessageControllerTest { + + @MockBean + private IDSBrokerService brokerService; + + @Autowired + private MockMvc mockMvc; + + @Test + public void sendConnectorUpdateMessage_unauthorized_rejectUnauthorized() throws Exception { + mockMvc.perform(post("/api/ids/query")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_validQuery_returnQueryAnswer() throws Exception { + /* ARRANGE */ + final var recipient = URI.create("https://someBroker").toString(); + final var query = "QUERY"; + + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol( + Protocol.HTTP_1_1).code(200).message(query) + .body(ResponseBody.create("ANSWER", MediaType + .parse("application/text"))) + .build(); + + Mockito.doReturn(response).when(brokerService) + .queryBroker(Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any()); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/query") + .param("recipient", recipient) + .content("SOME QUERY")) + .andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals("ANSWER", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_someProblem_returnIdsMessageFailed() throws Exception { + /* ARRANGE */ + final var recipient = URI.create("https://someBroker").toString(); + + Mockito.doThrow(IOException.class).when(brokerService) + .queryBroker(Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any(), + Mockito.any()); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/query") + .param("recipient", recipient) + .content("SOME QUERY")) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", + result.getResponse().getContentAsString()); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageControllerTest.java b/src/test/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageControllerTest.java new file mode 100644 index 000000000..87e8b7597 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/ResourceUnavailableMessageControllerTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import io.dataspaceconnector.services.ids.ConnectorService; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ResourceUnavailableMessageControllerTest { + @MockBean + private IDSBrokerService brokerService; + + @MockBean + private ConnectorService connectorService; + + @Autowired + private MockMvc mockMvc; + + private final UUID resourceId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + private final URI resourceURI = URI.create(resourceId.toString()); + private final Resource resource = getResource(); + private final String recipient = "https://someURL"; + + @Test + public void sendConnectorUpdateMessage_unauthorized_returnUnauthorized() throws Exception { + mockMvc.perform(post("/api/ids/resource/unavailable")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noRecipient_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = + mockMvc.perform(post("/api/ids/resource/unavailable") + .param("resourceId", resourceId.toString())).andExpect(status().isBadRequest()) + .andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noResourceId_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = + mockMvc.perform(post("/api/ids/resource/unavailable") + .param("recipient", "https://someUrl")).andExpect(status().isBadRequest()) + .andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_resourceNotFound_throws404() + throws Exception { + /* ARRANGE */ + Mockito.doReturn(Optional.empty()).when(connectorService).getOfferedResourceById(resourceURI); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/unavailable") + .param("recipient", "https://someURL") + .param("resourceId", resourceId.toString())) + .andExpect(status().isNotFound()).andReturn(); + + /* ASSERT */ + assertEquals("Resource 550e8400-e29b-11d4-a716-446655440000 not found.", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateAtBroker_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doThrow(IOException.class).when(brokerService).removeResourceFromBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/unavailable") + .param("recipient", "https://someURL") + .param("resourceId", resourceId.toString())) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", + result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_brokerEmptyResponseBody_throws500() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol(Protocol.HTTP_1_1).code(200).message("").build(); + + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doReturn(response).when(brokerService).removeResourceFromBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/unavailable") + .param("recipient", recipient) + .param("resourceId", resourceId.toString())) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", + result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_validRequest_returnsBrokerResponse() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol( + Protocol.HTTP_1_1).code(200).message("") + .body(ResponseBody.create("ANSWER", MediaType + .parse("application/text"))) + .build(); + + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doReturn(response).when(brokerService).removeResourceFromBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/unavailable") + .param("recipient", recipient) + .param("resourceId", resourceId.toString())) + .andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals("ANSWER", result.getResponse().getContentAsString()); + } + + private Resource getResource() { + return new ResourceBuilder(URI.create(resourceId.toString())).build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageControllerTest.java b/src/test/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageControllerTest.java new file mode 100644 index 000000000..7e3ef6c1e --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/messages/ResourceUpdateMessageControllerTest.java @@ -0,0 +1,194 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.messages; + +import java.io.IOException; +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; +import io.dataspaceconnector.services.ids.ConnectorService; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ResourceUpdateMessageControllerTest { + + @MockBean + private IDSBrokerService brokerService; + + @MockBean + private ConnectorService connectorService; + + @Autowired + private MockMvc mockMvc; + + private final UUID resourceId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + private final URI resourceURI = URI.create(resourceId.toString()); + private final Resource resource = getResource(); + private final String recipient = "https://someURL"; + + @Test + public void sendConnectorUpdateMessage_unauthorized_returnUnauthorized() throws Exception { + mockMvc.perform(post("/api/ids/resource/update")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noRecipient_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = + mockMvc.perform(post("/api/ids/resource/update") + .param("resourceId", resourceId.toString())).andExpect(status().isBadRequest()) + .andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_noResourceId_throws400() + throws Exception { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = + mockMvc.perform(post("/api/ids/resource/update") + .param("recipient", "https://someUrl")).andExpect(status().isBadRequest()) + .andReturn(); + + /* ASSERT */ + assertTrue(result.getResponse().getContentAsString().isEmpty()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_resourceNotFound_throws404() + throws Exception { + /* ARRANGE */ + Mockito.doReturn(Optional.empty()).when(connectorService).getOfferedResourceById(resourceURI); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/update") + .param("recipient", "https://someURL") + .param("resourceId", resourceId.toString())) + .andExpect(status().isNotFound()).andReturn(); + + /* ASSERT */ + assertEquals("Resource 550e8400-e29b-11d4-a716-446655440000 not found.", result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_failUpdateAtBroker_throws500() + throws Exception { + /* ARRANGE */ + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doThrow(IOException.class).when(brokerService).updateResourceAtBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/update") + .param("recipient", "https://someURL") + .param("resourceId", resourceId.toString())) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", + result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_brokerEmptyResponseBody_throws500() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol(Protocol.HTTP_1_1).code(200).message("").build(); + + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doReturn(response).when(brokerService).updateResourceAtBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/update") + .param("recipient", recipient) + .param("resourceId", resourceId.toString())) + .andExpect(status().isInternalServerError()).andReturn(); + + /* ASSERT */ + assertEquals("Ids message handling failed. null", + result.getResponse().getContentAsString()); + } + + @Test + @WithMockUser("ADMIN") + public void sendConnectorUpdateMessage_validRequest_returnsBrokerResponse() + throws Exception { + /* ARRANGE */ + final var response = + new Response.Builder().request(new Request.Builder().url(recipient).build()) + .protocol( + Protocol.HTTP_1_1).code(200).message("") + .body(ResponseBody.create("ANSWER", MediaType + .parse("application/text"))) + .build(); + + Mockito.doReturn(Optional.of(resource)).when(connectorService).getOfferedResourceById(Mockito.eq(resourceURI)); + Mockito.doReturn(response).when(brokerService).updateResourceAtBroker(Mockito.any(), + Mockito.eq(resource)); + + /* ACT */ + final var result = mockMvc.perform(post("/api/ids/resource/update") + .param("recipient", recipient) + .param("resourceId", resourceId.toString())) + .andExpect(status().isOk()).andReturn(); + + /* ASSERT */ + assertEquals("ANSWER", result.getResponse().getContentAsString()); + } + + private Resource getResource() { + return new ResourceBuilder(URI.create(resourceId.toString())).build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/AgreementControllerTest.java b/src/test/java/io/dataspaceconnector/controller/resources/AgreementControllerTest.java new file mode 100644 index 000000000..2a05a13c6 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/AgreementControllerTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.AgreementDesc; +import io.dataspaceconnector.view.AgreementViewAssembler; +import io.dataspaceconnector.services.resources.AgreementService; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.http.HttpStatus; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SpringBootTest(classes = {ResourceControllers.AgreementController.class}) +class AgreementControllerTest { + @MockBean + private AgreementService service; + + @MockBean + private AgreementViewAssembler assembler; + + @MockBean + private PagedResourcesAssembler pagedAssembler; + + @Autowired + @InjectMocks + private ResourceControllers.AgreementController controller; + + /** + * create. + */ + + @Test + public void create_null_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.create(null); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + @Test + public void create_validDesc_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.create(new AgreementDesc()); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + /** + * update. + */ + @Test + public void update_null_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.update(null, null); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + @Test + public void update_validInput_returnMethodNotAllowed() { + /* ARRANGE */ + final var id = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT */ + final var result = controller.update(id, new AgreementDesc()); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + /** + * delete. + */ + @Test + public void delete_null_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.delete(null); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + @Test + public void delete_anyId_returnMethodNotAllowed() { + /* ARRANGE */ + final var id = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT */ + final var result = controller.delete(id); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/ArtifactControllerTest.java b/src/test/java/io/dataspaceconnector/controller/resources/ArtifactControllerTest.java new file mode 100644 index 000000000..92003d40f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/ArtifactControllerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//package io.dataspaceconnector.controller.resources; +// +//import java.net.URI; +//import java.util.UUID; +// +//import io.dataspaceconnector.exceptions.ResourceNotFoundException; +//import io.dataspaceconnector.model.Artifact; +//import io.dataspaceconnector.model.QueryInput; +//import io.dataspaceconnector.services.ArtifactRetriever; +//import io.dataspaceconnector.services.BlockingArtifactReceiver; +//import io.dataspaceconnector.services.resources.ArtifactService; +//import io.dataspaceconnector.services.usagecontrol.PolicyVerifier; +//import io.dataspaceconnector.services.usagecontrol.VerificationResult; +//import io.dataspaceconnector.view.ArtifactViewAssembler; +//import org.junit.jupiter.api.Test; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.Mockito; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.data.web.PagedResourcesAssembler; +//import org.springframework.http.HttpStatus; +// +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertThrows; +// +//@SpringBootTest(classes = {ResourceControllers.ArtifactController.class}) +//class ArtifactControllerTest { +// @MockBean +// ArtifactService service; +// +// @MockBean +// private ArtifactViewAssembler assembler; +// +// @MockBean +// private PagedResourcesAssembler pagedAssembler; +// +// @MockBean +// private PolicyVerifier policyVerifier; +// +// @MockBean +// private ArtifactRetriever artifactRetriever; +// +// @Autowired +// @InjectMocks +// ResourceControllers.ArtifactController controller; +// +// @Test +// public void getData_null_throwIllegalArgumentException() { +// /* ARRANGE */ +// Mockito.when(policyVerifier.verify(Mockito.any())).thenReturn(VerificationResult.ALLOWED); +// Mockito.when(service.getData(policyVerifier, artifactRetriever, Mockito.isNull(), (QueryInput) Mockito.any())) +// .thenThrow(IllegalArgumentException.class); +// +// /* ACT && ASSERT */ +// assertThrows(IllegalArgumentException.class, () -> controller.getData(null, +// new QueryInput())); +// } +// +// @Test +// public void getData_unknownId_throwResourceNotFoundException() { +// /* ARRANGE */ +// final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); +// Mockito.when(service.getData(Mockito.eq(unknownUuid), (QueryInput) Mockito.any())) +// .thenThrow(ResourceNotFoundException.class); +// +// /* ACT */ +// assertThrows(ResourceNotFoundException.class, () -> controller.getData(unknownUuid, +// new QueryInput())); +// } +// +// @Test +// public void getData_knownId_returnData() { +// /* ARRANGE */ +// final var expected = "TEST"; +// final var knownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); +// Mockito.when(service.getData(Mockito.eq(knownUuid), (QueryInput) Mockito.any())).thenReturn(expected); +// +// /* ACT */ +// final var result = controller.getData(knownUuid, new QueryInput()); +// +// /* ASSERT */ +// assertEquals(expected, result.getBody()); +// } +// +// @Test +// public void getData_knownId_hasStatusCode200() { +// /* ARRANGE */ +// final var expected = "TEST"; +// final var knownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); +// Mockito.when(service.getData(Mockito.eq(knownUuid), (QueryInput) Mockito.any())).thenReturn(expected); +// +// /* ACT */ +// final var result = controller.getData(knownUuid, new QueryInput()); +// +// /* ASSERT */ +// assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue()); +// } +//} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest.java b/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest.java new file mode 100644 index 000000000..38a5201c2 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest.java @@ -0,0 +1,420 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.util.ArrayList; +import java.util.UUID; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.view.CatalogViewAssembler; +import io.dataspaceconnector.services.resources.CatalogService; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {ResourceControllers.CatalogController.class}) +class CatalogControllerTest { + @MockBean + private CatalogService catalogService; + + @SpyBean + private CatalogViewAssembler assembler; + + @SpyBean + private PagedResourcesAssembler pagedAssembler; + + @Autowired + private ResourceControllers.CatalogController controller; + + private CatalogDesc desc = getDesc(); + private CatalogDesc updatedDescOne = getUpdatedDesc(); + private Catalog catalogOne = getCatalogOne(); + private Catalog updatedCatalogOne = getUpdatedCatalogOne(); + private boolean doesCatalogOneExist = true; + final UUID unknownUUid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /** + * Setup + */ + + @BeforeEach + public void init() { + Mockito.when(catalogService.create(Mockito.eq(getDesc()))).thenReturn(catalogOne); + Mockito.when(catalogService.get(Mockito.eq(unknownUUid))) + .thenThrow(ResourceNotFoundException.class); + Mockito.when(catalogService.get(Mockito.eq(catalogOne.getId()))).thenReturn(catalogOne); + Mockito.when(catalogService.update(Mockito.eq(catalogOne.getId()), Mockito.eq(desc))) + .thenReturn(catalogOne); + Mockito.when(catalogService.update( + Mockito.eq(catalogOne.getId()), Mockito.eq(updatedDescOne))) + .thenReturn(updatedCatalogOne); + + Mockito.when(catalogService.update(Mockito.isNull(), Mockito.eq(new CatalogDesc()))) + .thenThrow(IllegalArgumentException.class); + Mockito.when(catalogService.update(Mockito.eq(catalogOne.getId()), Mockito.isNull())) + .thenThrow(IllegalArgumentException.class); + Mockito.when(catalogService.update(Mockito.eq(unknownUUid), Mockito.isNull())) + .thenThrow(IllegalArgumentException.class); + Mockito.when(catalogService.update(Mockito.eq(unknownUUid), Mockito.isNotNull())) + .thenThrow(ResourceNotFoundException.class); + Mockito.when(catalogService.update(Mockito.isNull(), Mockito.isNull())) + .thenThrow(IllegalArgumentException.class); + + Mockito.doThrow(IllegalArgumentException.class) + .when(catalogService) + .delete(Mockito.isNull()); + Mockito.doAnswer(invocation -> { + doesCatalogOneExist = false; + return null; + }) + .when(catalogService) + .delete(Mockito.eq(catalogOne.getId())); + } + + /** + * create + */ + + @Test + public void create_validDesc_hasStatusCode201() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.create(desc); + + /* ASSERT */ + assertEquals(HttpStatus.CREATED.value(), result.getStatusCodeValue()); + } + + @Test + public void create_validDesc_hasLocationHeader() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.create(desc); + + /* ASSERT */ + assertTrue(result.getHeaders().containsKey("Location")); + } + + @Test + public void create_nullDesc_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.create(null)); + } + + @Test + public void create_validDesc_returnValidCatalog() { + /* ARRANGE */ + final var catalogView = new CatalogViewAssembler().toModel(catalogService.create(desc)); + + /* ACT */ + final var result = controller.create(desc); + + /* ASSERT */ + assertEquals(catalogView, result.getBody()); + } + + /** + * get + */ + + @Test + public void get_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.get(null)); + } + + @Test + public void get_unknownId_throwsResourceNotFoundException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + assertThrows(ResourceNotFoundException.class, () -> controller.get(unknownUUid)); + } + + @Test + public void get_knownId_hasStatusCode200() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.get(catalogOne.getId()); + + /* ASSERT */ + assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue()); + } + + @Test + public void get_knownId_getCatalog() { + /* ARRANGE */ + final var catalogView = new CatalogViewAssembler().toModel(catalogOne); + + /* ACT */ + final var result = controller.get(catalogOne.getId()); + + /* ASSERT */ + assertEquals(catalogView, result.getBody()); + } + + /** + * update + */ + + @Test + public void update_nullResourceId_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows( + IllegalArgumentException.class, () -> controller.update(null, new CatalogDesc())); + } + + @Test + public void update_knownIdNullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows( + IllegalArgumentException.class, () -> controller.update(catalogOne.getId(), null)); + } + + @Test + public void update_unknownIdNullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.update(unknownUUid, null)); + } + + @Test + public void update_knownIdValidDesc_throwResourceNotFoundException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, () -> controller.update(unknownUUid, new CatalogDesc())); + } + + @Test + public void update_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.update(null, null)); + } + + @Test + public void update_sameDesc_returnSameElement() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + controller.update(catalogOne.getId(), desc); + + /* ASSERT */ + Mockito.verify(catalogService).update(catalogOne.getId(), desc); + } + + @Test + public void update_sameDesc_hasStatusCode204() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.update(catalogOne.getId(), desc); + + /* ASSERT */ + assertEquals(HttpStatus.NO_CONTENT.value(), result.getStatusCodeValue()); + } + + @Test + public void update_differentDesc_hasStatusCode204() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.update(catalogOne.getId(), updatedDescOne); + + /* ASSERT */ + assertEquals(HttpStatus.NO_CONTENT.value(), result.getStatusCodeValue()); + } + + @Test + public void update_differentDesc_doesUpdateElement() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + controller.update(catalogOne.getId(), updatedDescOne); + + /* ASSERT */ + Mockito.verify(catalogService).update(catalogOne.getId(), updatedDescOne); + } + + /** + * delete + */ + + @Test + public void delete_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.delete(null)); + } + + @Test + public void delete_unknownId_returnNoContentResponse() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.delete(unknownUUid); + + /* ASSERT */ + assertEquals(HttpStatus.NO_CONTENT.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + } + + @Test + public void delete_knownId_returnNoContentResponse() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = controller.delete(catalogOne.getId()); + + /* ASSERT */ + assertEquals(HttpStatus.NO_CONTENT.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + } + + @Test + public void delete_knownId_returnNoElement() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + controller.delete(catalogOne.getId()); + + /* ASSERT */ + assertFalse(doesCatalogOneExist); + } + + /** + * Utilities + */ + private CatalogDesc getDesc() { + final var desc = new CatalogDesc(); + desc.setTitle("Catalog"); + desc.setDescription("Some description"); + + return desc; + } + + private CatalogDesc getUpdatedDesc() { + final var desc = new CatalogDesc(); + desc.setTitle("Some other Catalog"); + desc.setDescription("Some other description"); + + return desc; + } + + @SneakyThrows + private Catalog getCatalogOne() { + final var desc = getDesc(); + final var constructor = Catalog.class.getConstructor(); + constructor.setAccessible(true); + + final var catalog = constructor.newInstance(); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, desc.getTitle()); + + final var descriptionField = catalog.getClass().getDeclaredField("description"); + descriptionField.setAccessible(true); + descriptionField.set(catalog, desc.getDescription()); + + final var offeredResourcesField = catalog.getClass().getDeclaredField("offeredResources"); + offeredResourcesField.setAccessible(true); + offeredResourcesField.set(catalog, new ArrayList()); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return catalog; + } + + @SneakyThrows + private Catalog getUpdatedCatalogOne() { + final var desc = getUpdatedDesc(); + final var constructor = Catalog.class.getConstructor(); + constructor.setAccessible(true); + + final var catalog = constructor.newInstance(); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, desc.getTitle()); + + final var descriptionField = catalog.getClass().getDeclaredField("description"); + descriptionField.setAccessible(true); + descriptionField.set(catalog, desc.getDescription()); + + final var offeredResourcesField = catalog.getClass().getDeclaredField("offeredResources"); + offeredResourcesField.setAccessible(true); + offeredResourcesField.set(catalog, new ArrayList()); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return catalog; + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest_getAll.java b/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest_getAll.java new file mode 100644 index 000000000..42419fd18 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/CatalogControllerTest_getAll.java @@ -0,0 +1,194 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.services.resources.CatalogService; +import io.dataspaceconnector.utils.Utils; +import io.dataspaceconnector.view.CatalogView; +import io.dataspaceconnector.view.CatalogViewAssembler; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedModel; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {ResourceControllers.CatalogController.class}) +class CatalogControllerTest_getAll { + @MockBean + private CatalogService service; + + @SpyBean + private CatalogViewAssembler assembler; + + @SpyBean + private PagedResourcesAssembler pagedAssembler; + + private List catalogList = new ArrayList<>(); + + @Autowired + // private MockMvc mockMvc; + private ResourceControllers.CatalogController controller; + + /** + * Setup + */ + + @BeforeEach + public void init() { + for (int i = 0; i < 50; i++) catalogList.add(getCatalog(String.valueOf(i))); + } + + /** + * getAll + */ + @Test + public void getAll_noElements_returnStatusCode200() { + /* ARRANGE */ + final var request = PageRequest.of(0, 1, Sort.unsorted()); + Mockito.when(service.getAll(Mockito.eq(request))).thenReturn(Page.empty()); + + /* ACT */ + final var result = controller.getAll(null, 1); + + /* ASSERT */ + assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue()); + } + + @Test + public void getAll_nullPage_returnFirstPage() { + /* ARRANGE */ + final var pageSize = 1; + final var request = PageRequest.of(0, pageSize); + Mockito.when(service.getAll(Mockito.eq(request))) + .thenReturn(Utils.toPage(catalogList, request)); + + /* ACT */ + final var result = controller.getAll(null, pageSize); + + /* ASSERT */ + final var body = (PagedModel) result.getBody(); + assertEquals(body.getLink("self").get().getHref(), body.getLink("first").get().getHref()); + assertEquals(1, body.getContent().size()); + assertEquals(new CatalogViewAssembler().toModel(catalogList.get(0)), + body.getContent().stream().findFirst().get()); + } + + @Test + public void getAll_firstPage_returnFirstPage() { + /* ARRANGE */ + final var pageSize = 1; + final var page = 0; + final var request = PageRequest.of(page, pageSize); + Mockito.when(service.getAll(Mockito.eq(request))) + .thenReturn(Utils.toPage(catalogList, request)); + + /* ACT */ + final var result = controller.getAll(page, pageSize); + + /* ASSERT */ + final var body = (PagedModel) result.getBody(); + assertEquals(page, body.getMetadata().getNumber()); + } + + @Test + public void getAll_validPage_returnValidPage() { + /* ARRANGE */ + final var pageSize = 13; + final var page = 3; + final var request = PageRequest.of(page, pageSize); + Mockito.when(service.getAll(Mockito.eq(request))) + .thenReturn(Utils.toPage(catalogList, request)); + + /* ACT */ + final var result = controller.getAll(page, pageSize); + + /* ASSERT */ + final var body = (PagedModel) result.getBody(); + assertEquals(page, body.getMetadata().getNumber()); + } + + @Test + public void getAll_toFarPage_returnEmptyPage() { + /* ARRANGE */ + final var pageSize = 1; + final var toFar = catalogList.size() + 1; + final var request = PageRequest.of(toFar, pageSize); + final var returnPage = Utils.toPage(catalogList, request); + Mockito.when(service.getAll(Mockito.eq(request))).thenReturn(returnPage); + + /* ACT */ + final var result = controller.getAll(toFar, pageSize); + + /* ASSERT */ + final var body = (PagedModel) result.getBody(); + assertEquals(1, body.getContent().size()); + } + + @Test + public void getAll_toEarlyPage_returnEmptyPage() { + /* ARRANGE */ + final var pageSize = 1; + final var toEarly = -1; + final var request = PageRequest.of(0, pageSize); + final var returnPage = Utils.toPage(catalogList, request); + Mockito.when(service.getAll(Mockito.eq(request))).thenReturn(returnPage); + + /* ACT */ + final var result = controller.getAll(toEarly, pageSize); + + /* ASSERT */ + + final var body = (PagedModel) result.getBody(); + assertEquals(0, body.getMetadata().getNumber()); + } + + /** + * Utilities + */ + @SneakyThrows + private Catalog getCatalog(final String title) { + final var constructor = Catalog.class.getConstructor(); + constructor.setAccessible(true); + + final var catalog = constructor.newInstance(); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, title); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.randomUUID()); + + return catalog; + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/RepresentationsToArtifactsTest.java b/src/test/java/io/dataspaceconnector/controller/resources/RepresentationsToArtifactsTest.java new file mode 100644 index 000000000..afd7893d4 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/RepresentationsToArtifactsTest.java @@ -0,0 +1,211 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactImpl; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.services.resources.RelationServices; +import io.dataspaceconnector.view.ArtifactView; +import io.dataspaceconnector.utils.Utils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {RelationControllers.RepresentationsToArtifacts.class}) +class RepresentationsToArtifactsTest { + + @MockBean + RelationServices.RepresentationArtifactLinker linker; + + @MockBean + RepresentationModelAssembler assembler; + + @MockBean + PagedResourcesAssembler pagedResourcesAssembler; + + @Autowired + @InjectMocks + private RelationControllers.RepresentationsToArtifacts controller; + + private Representation representation = getRepresentation("Owner"); + private List artifacts = new ArrayList<>(); + + /** + * Setup. + */ + @BeforeEach + public void init() { + for (int i = 0; i < 50; i++) artifacts.add(getArtifact(String.valueOf(i))); + } + + /** + * getResource. + */ + + @Test + public void getResource_nullId_throwIllegalArgumentException() { + /* ARRANGE */ + Mockito.when(linker.get(Mockito.isNull(), Mockito.any())).thenThrow(IllegalArgumentException.class); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.getResource(null, 0, null)); + } + + @Test + public void getResource_unknownId_throwResourceNotFoundException() { + /* ARRANGE */ + final UUID unknownUUid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + Mockito.when(linker.get(Mockito.eq(unknownUUid), Mockito.any())).thenThrow(ResourceNotFoundException.class); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, () -> controller.getResource(unknownUUid, null, null)); + } + + @Test + public void getResource_knownIdNoChildren_returnEmptyPage() { + /* ARRANGE */ + final UUID knownUUID = UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e"); + Mockito.when(linker.get(Mockito.eq(knownUUID), Mockito.any())).thenReturn(Utils.toPage(new ArrayList<>(), Pageable.unpaged())); + + /* ACT */ + final var result = controller.getResource(knownUUID, null, null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(HttpStatus.OK.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + } + + /** + * addResource. + */ + + @Test + public void addResources_nullOwnerId_throwIllegalArgumentException() { + /* ARRANGE */ + Mockito.doThrow(IllegalArgumentException.class).when(linker).add(Mockito.isNull(), Mockito.any()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.addResources(null, new ArrayList<>())); + } + + @Test + public void addResources_nullList_throwIllegalArgumentException() { + /* ARRANGE */ + Mockito.doThrow(IllegalArgumentException.class).when(linker).add(Mockito.any(), Mockito.isNull()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> controller.addResources(UUID.randomUUID(), null)); + } + + @Test + public void addResources_unknownId_throwResourceNotFoundException() { + /* ARRANGE */ + final UUID unknownUUid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + Mockito.doThrow(ResourceNotFoundException.class).when(linker).add(Mockito.eq(unknownUUid), Mockito.any()); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, () -> controller.addResources(unknownUUid, new ArrayList<>())); + } + +// @Test +// public void addResources_validInput_returnModifiedResource() { +// /* ARRANGE */ +// final UUID knownUUID = UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e"); +// final var validIdList = new ArrayList(); +// validIdList.add(URI.create("https://randompath/363730ec-dcea-45a0-9469-296b868e6a4b")); +// validIdList.add(URI.create("https://rando/path/acb249b6-7e51-4d50-a162-0bb71ecd9c2c")); +// validIdList.add(URI.create("https://6c0a6b4e-5713-4264-98c2-adab3a6b8782")); +// +// Mockito.when(linker.get(knownUUID, Pageable.unpaged())).thenReturn(validIdList); +// +// /* ACT */ +// final var result = controller.addResources(knownUUID, validIdList); +// +// /* ASSERT */ +// +// } + + /** + * Utilities + */ + @SneakyThrows + private Representation getRepresentation(final String title) { + final var constructor = Representation.class.getConstructor(); + constructor.setAccessible(true); + + final var representation = constructor.newInstance(); + + final var titleField = representation.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(representation, title); + + final var artifactsField = representation.getClass().getDeclaredField("artifacts"); + artifactsField.setAccessible(true); + artifactsField.set(representation, new ArrayList()); + + final var idField = representation.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + final var additionalField = + representation.getClass().getSuperclass().getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(representation, new HashMap<>()); + + return representation; + } + + @SneakyThrows + private ArtifactImpl getArtifact(String title) { + final var constructor = ArtifactImpl.class.getConstructor(); + constructor.setAccessible(true); + + final var artifact = constructor.newInstance(); + + final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(artifact, title); + + final var idField = + artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.randomUUID()); + + return artifact; + } +} diff --git a/src/test/java/io/dataspaceconnector/controller/resources/RequestedResourceControllerTest.java b/src/test/java/io/dataspaceconnector/controller/resources/RequestedResourceControllerTest.java new file mode 100644 index 000000000..98363a0d3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/controller/resources/RequestedResourceControllerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.controller.resources; + +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.view.RequestedResourceViewAssembler; +import io.dataspaceconnector.services.resources.ResourceService; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SpringBootTest(classes = {ResourceControllers.RequestedResourceController.class}) +class RequestedResourceControllerTest { + @MockBean + private ResourceService service; + + @MockBean + private RequestedResourceViewAssembler assembler; + + @MockBean + private PagedResourcesAssembler pagedAssembler; + + @Autowired + @InjectMocks + private ResourceControllers.RequestedResourceController controller; + + @Test + public void create_null_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.create(null); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } + + @Test + public void create_validDesc_returnMethodNotAllowed() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = controller.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(HttpStatus.METHOD_NOT_ALLOWED.value(), result.getStatusCodeValue()); + assertNull(result.getBody()); + Mockito.verifyNoInteractions(service); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/ContractExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/ContractExceptionTest.java new file mode 100644 index 000000000..96bf7df87 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/ContractExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ContractExceptionTest { + + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new ContractException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/InvalidInputExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/InvalidInputExceptionTest.java new file mode 100644 index 000000000..891ad0c20 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/InvalidInputExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class InvalidInputExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new InvalidInputException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new InvalidInputException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/InvalidResourceExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/InvalidResourceExceptionTest.java new file mode 100644 index 000000000..9c6b10608 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/InvalidResourceExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class InvalidResourceExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new InvalidResourceException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/MessageBuilderExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/MessageBuilderExceptionTest.java new file mode 100644 index 000000000..65aa58b10 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/MessageBuilderExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageBuilderExceptionTest { + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new MessageBuilderException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/MessageEmptyExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/MessageEmptyExceptionTest.java new file mode 100644 index 000000000..b1ba35aa5 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/MessageEmptyExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageEmptyExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new MessageEmptyException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new MessageEmptyException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/MessageExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/MessageExceptionTest.java new file mode 100644 index 000000000..4214f616e --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/MessageExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageExceptionTest { + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new MessageException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/MessageRequestExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/MessageRequestExceptionTest.java new file mode 100644 index 000000000..a3e89ba45 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/MessageRequestExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageRequestExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new MessageRequestException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new MessageRequestException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/MessageResponseExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/MessageResponseExceptionTest.java new file mode 100644 index 000000000..0afee0148 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/MessageResponseExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageResponseExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new MessageResponseException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new MessageResponseException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/PolicyExecutionExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/PolicyExecutionExceptionTest.java new file mode 100644 index 000000000..27d51c8f8 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/PolicyExecutionExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PolicyExecutionExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new PolicyExecutionException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/PolicyRestrictionExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/PolicyRestrictionExceptionTest.java new file mode 100644 index 000000000..73c29c7dd --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/PolicyRestrictionExceptionTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PolicyRestrictionExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = ErrorMessages.CONTRACT_NULL; + + /* ACT */ + final var exception = new PolicyRestrictionException(msg); + + /* ASSERT */ + assertEquals(msg.toString(), exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = ErrorMessages.CONTRACT_NULL; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new PolicyRestrictionException(msg, someError); + + /* ASSERT */ + assertEquals(msg.toString(), exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/RdfBuilderExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/RdfBuilderExceptionTest.java new file mode 100644 index 000000000..398656898 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/RdfBuilderExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class RdfBuilderExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = ErrorMessages.CONTRACT_NULL; + + /* ACT */ + final var exception = new RdfBuilderException(msg); + + /* ASSERT */ + assertEquals(msg.toString(), exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/ResourceExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/ResourceExceptionTest.java new file mode 100644 index 000000000..5d7d3cdbb --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/ResourceExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ResourceExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new ResourceException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/ResourceNotFoundExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/ResourceNotFoundExceptionTest.java new file mode 100644 index 000000000..81fe990bf --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/ResourceNotFoundExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ResourceNotFoundExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new ResourceNotFoundException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new ResourceNotFoundException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/SelfLinkCreationExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/SelfLinkCreationExceptionTest.java new file mode 100644 index 000000000..dcff986ca --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/SelfLinkCreationExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class SelfLinkCreationExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = ErrorMessages.CONTRACT_NULL; + + /* ACT */ + final var exception = new SelfLinkCreationException(msg); + + /* ASSERT */ + assertEquals(msg.toString(), exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/UUIDCreationExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/UUIDCreationExceptionTest.java new file mode 100644 index 000000000..40ef46325 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/UUIDCreationExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UUIDCreationExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new UUIDCreationException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/UUIDFormatExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/UUIDFormatExceptionTest.java new file mode 100644 index 000000000..379d2c6c8 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/UUIDFormatExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UUIDFormatExceptionTest { + @Test + public void constructor_someMsgAndsSomeException_holdsMsgAndException() { + /* ARRANGE */ + final var msg = "Some msg"; + final var someError = new RuntimeException("WELL?"); + + /* ACT */ + final var exception = new UUIDFormatException(msg, someError); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + assertEquals(someError, exception.getCause()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/UnreachableLineExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/UnreachableLineExceptionTest.java new file mode 100644 index 000000000..3fb089100 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/UnreachableLineExceptionTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import io.dataspaceconnector.utils.ErrorMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnreachableLineExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new UnreachableLineException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } + + @Test + public void constructor_someErrorMsg_holdsMsg() { + /* ARRANGE */ + final var msg = ErrorMessages.CONTRACT_NULL; + + /* ACT */ + final var exception = new UnreachableLineException(msg); + + /* ASSERT */ + assertEquals(msg.toString(), exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/UnsupportedPatternExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/UnsupportedPatternExceptionTest.java new file mode 100644 index 000000000..d4095f5bb --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/UnsupportedPatternExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnsupportedPatternExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new UnsupportedPatternException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/exceptions/VersionNotSupportedExceptionTest.java b/src/test/java/io/dataspaceconnector/exceptions/VersionNotSupportedExceptionTest.java new file mode 100644 index 000000000..dcc44905c --- /dev/null +++ b/src/test/java/io/dataspaceconnector/exceptions/VersionNotSupportedExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class VersionNotSupportedExceptionTest { + @Test + public void constructor_someMsg_holdsMsg() { + /* ARRANGE */ + final var msg = "Some msg"; + + /* ACT */ + final var exception = new VersionNotSupportedException(msg); + + /* ASSERT */ + assertEquals(msg, exception.getMessage()); + } +} diff --git a/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandlerTest.java b/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandlerTest.java new file mode 100644 index 000000000..62cca65d9 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceEventHandlerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.ApplicationEventPublisher; + +@SpringBootTest(classes = {HttpTraceEventHandler.class}) +public class HttpTraceEventHandlerTest { + + @MockBean + private ApplicationEventPublisher publisher; + + @Autowired + private HttpTraceEventHandler handler; + + @Test + public void sendHttpTraceEvent_validTrace_publishHttpTraceEvent() { + /* ARRANGE */ + final var trace = new HttpTrace(); + trace.setBody("HELLO"); + + /* ACT */ + handler.sendHttpTraceEvent(trace); + + /* ASSERT */ + Mockito.verify(publisher, Mockito.atMostOnce()).publishEvent(Mockito.eq(trace)); + } + + @Test + public void sendHttpTraceEvent_null_dontPublishEvent() { + /* ARRANGE */ + /* ACT */ + handler.sendHttpTraceEvent(null); + + /* ASSERT */ + Mockito.verify(publisher, Mockito.never()).publishEvent(Mockito.any()); + } + + @Test + public void handleHttpTraceEvent_validTrace_logIt() { + /* ARRANGE */ + final var trace = new HttpTrace(); + trace.setBody("HELLO"); + + /* ACT */ + handler.handleHttpTraceEvent(trace); + + /* ASSERT */ + } +} diff --git a/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilterTest.java b/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilterTest.java new file mode 100644 index 000000000..e7822b66e --- /dev/null +++ b/src/test/java/io/dataspaceconnector/filter/httptracing/HttpTraceFilterTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing; + +import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; + +import io.dataspaceconnector.filter.httptracing.internal.RequestWrapper; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +public class HttpTraceFilterTest { + + @Mock + HttpTraceEventHandler eventHandler; + + @Mock + MockHttpServletRequest request; + + @Mock + MockHttpServletResponse response; + + @Mock + MockFilterChain filterChain; + + @Captor + private ArgumentCaptor traceCaptor; + + @InjectMocks + private HttpTraceFilter filter; + + @Test + public void doFilterInternal_validRequest_captureRequest() throws Exception { + /* ARRANGE */ + final var now = ZonedDateTime.now(); + final var headers = new HashMap<>(); + headers.put("SOME", "HEADER"); + + final var headerNames = Collections.enumeration(headers.keySet()); + + Mockito.doReturn(headerNames).when(request).getHeaderNames(); + Mockito.doReturn("/URI").when(request).getRequestURI(); + Mockito.doReturn("METHOD").when(request).getMethod(); + Mockito.doReturn("CLIENT").when(request).getRemoteAddr(); + + final var params = new HashMap<>(); + params.put("OTHER", "PARAMETER"); + + final var parameterNames = Collections.enumeration(params.keySet()); + Mockito.doReturn(parameterNames).when(request).getParameterNames(); + + final var customServletIS = RequestWrapper.class.getDeclaredClasses()[0].getDeclaredConstructor(byte[].class); + customServletIS.setAccessible(true); + final var requestBody = customServletIS.newInstance("BODY".getBytes(StandardCharsets.UTF_8)); + + Mockito.doReturn(requestBody).when(request).getInputStream(); + Mockito.doReturn(StandardCharsets.UTF_8.name()).when(request).getCharacterEncoding(); + + /* ACT */ + filter.doFilter(request, response, filterChain); + + /* ASSERT */ + Mockito.verify(eventHandler, Mockito.times(2)).sendHttpTraceEvent(traceCaptor.capture()); + final var traces = traceCaptor.getAllValues(); + + assertEquals(2, traces.size()); + + final var requestTrace = traces.get(0); + assertNotNull(requestTrace.getTraceId()); + assertTrue(requestTrace.getTimestamp().isAfter(now)); + assertEquals("/URI", requestTrace.getUrl()); + assertEquals("METHOD", requestTrace.getMethod()); + assertEquals("CLIENT", requestTrace.getClient()); + + final var responseTrace = traces.get(1); + assertEquals(responseTrace.getTraceId(), requestTrace.getTraceId()); + assertTrue(responseTrace.getTimestamp().isAfter(requestTrace.getTimestamp())); + assertNull(responseTrace.getMethod()); + assertNull(responseTrace.getUrl()); + assertEquals("ERROR", responseTrace.getBody()); + assertEquals(0, responseTrace.getHeaders().size()); + assertEquals(0, responseTrace.getStatus()); + assertNull(responseTrace.getClient()); + assertNull(responseTrace.getParameterMap()); + } + +// @Test +// public void doFilterInternal_validRequest_captureResponse() throws Exception { +// /* ARRANGE */ +// final var headers = new HashMap<>(); +// headers.put("SOME", "HEADER"); +// +// final var headerNames = Collections.enumeration(headers.keySet()); +// +// Mockito.doReturn(headerNames).when(request).getHeaderNames(); +// Mockito.doReturn("/URI").when(request).getRequestURI(); +// Mockito.doReturn("METHOD").when(request).getMethod(); +// Mockito.doReturn("CLIENT").when(request).getRemoteAddr(); +// +// final var params = new HashMap<>(); +// params.put("OTHER", "PARAMETER"); +// +// final var parameterNames = Collections.enumeration(params.keySet()); +// Mockito.doReturn(parameterNames).when(request).getParameterNames(); +// +// final var customServletIS = RequestWrapper.class.getDeclaredClasses()[0].getDeclaredConstructor(byte[].class); +// customServletIS.setAccessible(true); +// final var requestBody = customServletIS.newInstance("BODY".getBytes(StandardCharsets.UTF_8)); +// +// Mockito.doReturn(requestBody).when(request).getInputStream(); +// Mockito.doReturn(StandardCharsets.UTF_8.name()).when(request).getCharacterEncoding(); +// +// /* +// * BUILD RESPONSE +// */ +// +// final var responseHeaders = new HashMap<>(); +// responseHeaders.put("SOMERESPONSE", "HEADERRESPONSE"); +// final var responseHeaderNames = (Collection) responseHeaders.keySet(); +// Mockito.doReturn(responseHeaderNames).when(response).getHeaderNames(); +// Mockito.doReturn("HEADERRESPONSE").when(response).getHeader(Mockito.eq("SOMERESPONSE")); +// +// Mockito.doReturn(42).when(response).getStatus(); +// Mockito.doReturn("SOME BODY".getBytes(StandardCharsets.UTF_8)).when(response).getContentAsByteArray(); +// Mockito.doReturn("SOME BODY".getBytes(StandardCharsets.UTF_8).length).when(response).getBufferSize(); +// Mockito.doReturn(StandardCharsets.UTF_8.toString()).when(response).getCharacterEncoding(); +// +// /* ACT */ +// filter.doFilter(request, response, filterChain); +// +// /* ASSERT */ +// Mockito.verify(eventHandler, Mockito.times(2)).sendHttpTraceEvent(traceCaptor.capture()); +// final var traces = traceCaptor.getAllValues(); +// +// assertEquals(2, traces.size()); +// +// final var requestTrace = traces.get(0); +// final var responseTrace = traces.get(1); +// assertEquals(responseTrace.getTraceId(), requestTrace.getTraceId()); +// assertTrue(responseTrace.getTimestamp().isAfter(requestTrace.getTimestamp())); +// assertNull(responseTrace.getMethod()); +// assertNull(responseTrace.getUrl()); +// assertEquals("ERROR", responseTrace.getBody()); +// assertEquals(1, responseTrace.getHeaders().size()); +// assertEquals("HEADERRESPONSE", responseTrace.getHeaders().get("SOMERESPONSE")); +// assertEquals(42, responseTrace.getStatus()); +// assertNull(responseTrace.getClient()); +// assertNull(responseTrace.getParameterMap()); +// } +} diff --git a/src/test/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapperTest.java b/src/test/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapperTest.java new file mode 100644 index 000000000..aa93a7bfd --- /dev/null +++ b/src/test/java/io/dataspaceconnector/filter/httptracing/internal/RequestWrapperTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.filter.httptracing.internal; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestWrapperTest { + + @Test + public void getRequestBody_copyBody_returnContent() throws IOException { + /* ARRANGE */ + final var request = new MockHttpServletRequest(); + request.setContent("HELLO".getBytes(StandardCharsets.UTF_8)); + final var wrapper = new RequestWrapper(request); + + /* ACT */ + final var result = wrapper.getRequestBody(); + + /* ASSERT */ + assertTrue(Arrays.equals("HELLO".getBytes(StandardCharsets.UTF_8), result)); + } + + @Test + public void getInputStream_copyBody_returnContent() throws IOException { + /* ARRANGE */ + final var request = new MockHttpServletRequest(); + request.setContent("HELLO".getBytes(StandardCharsets.UTF_8)); + final var wrapper = new RequestWrapper(request); + + /* ACT */ + final var result = wrapper.getInputStream(); + + /* ASSERT */ + assertTrue(Arrays.equals("HELLO".getBytes(StandardCharsets.UTF_8), result.readAllBytes())); + assertTrue(result.isFinished()); + assertTrue(result.isReady()); + } + + @Test + public void getReader_copyBody_returnContent() throws IOException { + /* ARRANGE */ + final var request = new MockHttpServletRequest(); + request.setContent("HELLO".getBytes(StandardCharsets.UTF_8)); + final var wrapper = new RequestWrapper(request); + wrapper.getRequestBody(); + + final var reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream("HELLO".getBytes(StandardCharsets.UTF_8)))); + + /* ACT && ASSERT */ + final var result = wrapper.getReader(); + + while (true) { + final var x = result.read(); + final var y = reader.read(); + assertEquals(x, y); + if(x == -1 || y == -1) { + break; + } + } + } +} diff --git a/src/test/java/io/dataspaceconnector/model/AbstractDescriptionTest.java b/src/test/java/io/dataspaceconnector/model/AbstractDescriptionTest.java new file mode 100644 index 000000000..55c4e8ebb --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/AbstractDescriptionTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AbstractDescriptionTest { + @Test + public void defaultConstructor_nothing_nullAdditional() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new AbstractDescription(); + + /* ASSERT */ + assertNull(result.getAdditional()); + } + + @Test + public void addOverflow_repeatSameInsert_oneKeyValuePair() { + /* ARRANGE */ + final var key = "Some key"; + final var value = "Some value"; + + /* ACT */ + final var result = new AbstractDescription(); + result.addOverflow(key, value); + result.addOverflow(key, value); + + /* ASSERT */ + assertEquals(1, result.getAdditional().size()); + assertTrue(result.getAdditional().keySet().contains(key)); + assertEquals(value, result.getAdditional().get(key)); + } + + @Test + public void addOverflow_nullKey_noInsert() { + /* ARRANGE */ + final var value = "Some value"; + + /* ACT */ + final var result = new AbstractDescription(); + result.addOverflow(null, value); + + /* ASSERT */ + assertEquals(0, result.getAdditional().size()); + } + + @Test + public void addOverflow_nullValue_noInsert() { + /* ARRANGE */ + final var key = "Some key"; + + /* ACT */ + final var result = new AbstractDescription(); + result.addOverflow(key, null); + + /* ASSERT */ + assertEquals(0, result.getAdditional().size()); + } + + @Test + public void addOverflow_null_noInsert() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new AbstractDescription(); + result.addOverflow(null, null); + + /* ASSERT */ + assertEquals(0, result.getAdditional().size()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(AbstractDescription.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/AbstractEntityTest.java b/src/test/java/io/dataspaceconnector/model/AbstractEntityTest.java new file mode 100644 index 000000000..2ea9d9a70 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/AbstractEntityTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class AbstractEntityTest { + + @Test + public void verify_equals() { + EqualsVerifier.forClass(AbstractEntity.class).verify(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/model/AgreementFactoryTest.java b/src/test/java/io/dataspaceconnector/model/AgreementFactoryTest.java new file mode 100644 index 000000000..169dc7c76 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/AgreementFactoryTest.java @@ -0,0 +1,361 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AgreementFactoryTest { + + private AgreementFactory factory; + + @BeforeEach + public void init() { + this.factory = new AgreementFactory(); + } + + @Test + public void default_value_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", AgreementFactory.DEFAULT_VALUE); + } + + @Test + public void default_remoteId_is_genesis() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create("genesis"), AgreementFactory.DEFAULT_REMOTE_ID); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new AgreementDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new AgreementDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new AgreementDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_artifactsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new AgreementDesc()); + + /* ASSERT */ + assertEquals(0, result.getArtifacts().size()); + } + + /** + * remoteId. + */ + + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(AgreementFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setRemoteId(URI.create("uri")); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + factory.update(agreement, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), agreement.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setRemoteId(URI.create("uri")); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, new AgreementDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * isConfirmed. + */ + + @Test + public void update_differentConfirmed_setConfirmed() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setConfirmed(true); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + factory.update(agreement, desc); + + /* ASSERT */ + assertEquals(desc.isConfirmed(), agreement.isConfirmed()); + } + + @Test + public void update_differentConfirmed_returnTrue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setConfirmed(true); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameConfirmed_returnFalse() { + /* ARRANGE */ + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, new AgreementDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * Value. + */ + + @Test + public void create_nullValue_defaultValue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setValue(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(AgreementFactory.DEFAULT_VALUE, result.getValue()); + } + + @Test + public void update_differentValue_setValue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setValue("Some Value"); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + factory.update(agreement, desc); + + /* ASSERT */ + assertEquals(desc.getValue(), agreement.getValue()); + } + + @Test + public void update_differentValue_returnTrue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setValue("Some Value"); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameValue_returnFalse() { + /* ARRANGE */ + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, new AgreementDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + factory.update(agreement, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), agreement.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new AgreementDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var agreement = factory.create(new AgreementDesc()); + + /* ACT */ + final var result = factory.update(agreement, new AgreementDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + + /** + * update inputs. + */ + + @Test + public void update_nullAgreement_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new AgreementDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var agreement = factory.create(new AgreementDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(agreement, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ArtifactDescTest.java b/src/test/java/io/dataspaceconnector/model/ArtifactDescTest.java new file mode 100644 index 000000000..070d26a18 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ArtifactDescTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class ArtifactDescTest { + + @Test + public void equals_verify() { + EqualsVerifier.simple().forClass(ArtifactDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ArtifactFactoryTest.java b/src/test/java/io/dataspaceconnector/model/ArtifactFactoryTest.java new file mode 100644 index 000000000..b692f64f0 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ArtifactFactoryTest.java @@ -0,0 +1,709 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class ArtifactFactoryTest { + + private ArtifactFactory factory; + + @BeforeEach + public void init() { + this.factory = new ArtifactFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ArtifactFactory.DEFAULT_TITLE); + } + + @Test + public void default_remoteId_is_genesis() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create("genesis"), ArtifactFactory.DEFAULT_REMOTE_ID); + } + + @Test + public void default_autoDownload_is_false() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertFalse(ArtifactFactory.DEFAULT_AUTO_DOWNLOAD); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_agreementsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertEquals(0, result.getAgreements().size()); + } + + @Test + public void create_validDesc_representationsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertEquals(0, result.getRepresentations().size()); + } + + /** + * remoteId. + */ + + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ArtifactFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setRemoteId(URI.create("uri")); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), artifact.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setRemoteId(URI.create("uri")); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, new ArtifactDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ArtifactFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setTitle("Random Title"); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), artifact.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setTitle("Random Title"); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, new ArtifactDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * autoDownload. + */ + + @Test + public void update_differentAutomatedDownload_setAutomatedDownload() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAutomatedDownload(true); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertEquals(desc.isAutomatedDownload(), artifact.isAutomatedDownload()); + } + + @Test + public void update_differentAutomatedDownload_returnTrue() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAutomatedDownload(true); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAutomatedDownload_returnFalse() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAutomatedDownload(true); + + final var artifact = factory.create(desc); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), artifact.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, new ArtifactDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullArtifact_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new ArtifactDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contract = factory.create(new ArtifactDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contract, null)); + } + + /** + * num accessed + */ + + @Test + public void create_num_accessed_is_0 () { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ArtifactDesc()); + + /* ASSERT */ + assertEquals(0, result.getNumAccessed()); + } + + /** + * access url + */ + + @Test + public void create_nullAccessUrl_isLocalData() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(null); + + /* ACT */ + final var result = (ArtifactImpl) factory.create(desc); + + /* ASSERT */ + assertTrue(((ArtifactImpl)result).getData() instanceof LocalData); + } + + @Test + public void create_setEmptyAccessUrl_isLocalData() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://")); + + /* ACT */ + final var result = (ArtifactImpl) factory.create(desc); + + /* ASSERT */ + assertTrue(((ArtifactImpl)result).getData() instanceof LocalData); + } + + @Test + public void create_setAccessUrl_isRemoteData() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + + /* ACT */ + final var result = (ArtifactImpl) factory.create(desc); + + /* ASSERT */ + assertTrue(((ArtifactImpl)result).getData() instanceof RemoteData); + } + + @Test + public void update_setAccessUrlNull_changeToLocalData() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + + final var artifact = factory.create(desc); + + /* ACT */ + factory.update(artifact, new ArtifactDesc()); + + /* ASSERT */ + assertTrue(((ArtifactImpl)artifact).getData() instanceof LocalData); + } + + @Test + public void update_setAccessUrl_changeToRemoteData() throws MalformedURLException { + /* ARRANGE */ + final var artifact = factory.create(new ArtifactDesc()); + + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertTrue(((ArtifactImpl)artifact).getData() instanceof RemoteData); + } + + /** + * local data + */ + + @Test + public void create_nullValue_returnEmpty() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setValue(null); + + /* ACT */ + final var result = (ArtifactImpl) factory.create(desc); + + /* ASSERT */ + assertNull(((LocalData)((ArtifactImpl)result).getData()).getValue()); + } + + @Test + public void update_setValue_returnValue() { + /* ARRANGE */ + final var artifact = (ArtifactImpl) factory.create(new ArtifactDesc()); + + final var desc = new ArtifactDesc(); + desc.setValue("Some Value"); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + assertTrue(Arrays.equals(desc.getValue().getBytes(StandardCharsets.UTF_16), ((LocalData)((ArtifactImpl)artifact).getData()).getValue())); + } + + @Test + public void update_differentValue_returnTrue() { + /* ARRANGE */ + final var artifact = (ArtifactImpl) factory.create(new ArtifactDesc()); + + final var desc = new ArtifactDesc(); + desc.setValue("Random Value"); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameValue_returnFalse() { + /* ARRANGE */ + final var artifact = (ArtifactImpl) factory.create(new ArtifactDesc()); + + /* ACT */ + final var result = factory.update(artifact, new ArtifactDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * access url + */ + + @Test + public void update_differentAccessUrl_setAccessUrl() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + + final var artifact = factory.create(new ArtifactDesc()); + + /* ACT */ + factory.update(artifact, desc); + + /* ASSERT */ + final var data = (RemoteData)((ArtifactImpl)artifact).getData(); + assertEquals(desc.getAccessUrl(), data.getAccessUrl()); + } + + /** + * username + */ + + @Test + public void create_nullUsername_nullUsername() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setUsername(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + final var data = (RemoteData)((ArtifactImpl)result).getData(); + assertNull(data.getUsername()); + } + + @Test + public void update_differentUsername_setUsername() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setUsername("Random Username"); + + final var artifact = factory.create(desc); + + final var updateDesc = new ArtifactDesc(); + updateDesc.setAccessUrl(new URL("https://localhost:8080/")); + updateDesc.setUsername("Random Different Username"); + + /* ACT */ + factory.update(artifact, updateDesc); + + /* ASSERT */ + final var data = (RemoteData)((ArtifactImpl)artifact).getData(); + assertEquals(updateDesc.getUsername(), data.getUsername()); + } + + @Test + public void update_differentUsername_returnTrue() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setUsername("Random Username"); + + final var artifact = factory.create(desc); + + final var updateDesc = new ArtifactDesc(); + updateDesc.setAccessUrl(new URL("https://localhost:8080/")); + updateDesc.setUsername("Random Different Username"); + + /* ACT */ + final var result = factory.update(artifact, updateDesc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameUsername_returnFalse() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setUsername("Random Username"); + + final var artifact = factory.create(desc); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * password + */ + + @Test + public void create_nullPassword_nullPassword() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setPassword(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + final var data = (RemoteData)((ArtifactImpl)result).getData(); + assertNull(data.getPassword()); + } + + @Test + public void update_differentPassword_setPassword() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setPassword("Random Password"); + + final var artifact = factory.create(desc); + + final var updateDesc = new ArtifactDesc(); + updateDesc.setAccessUrl(new URL("https://localhost:8080/")); + updateDesc.setPassword("Random Different Password"); + + /* ACT */ + factory.update(artifact, updateDesc); + + /* ASSERT */ + final var data = (RemoteData)((ArtifactImpl)artifact).getData(); + assertEquals(updateDesc.getPassword(), data.getPassword()); + } + + @Test + public void update_differentPassword_returnTrue() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setPassword("Random Password"); + + final var artifact = factory.create(desc); + + final var updateDesc = new ArtifactDesc(); + updateDesc.setAccessUrl(new URL("https://localhost:8080/")); + updateDesc.setPassword("Random Different Password"); + + /* ACT */ + final var result = factory.update(artifact, updateDesc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_samePassword_returnFalse() throws MalformedURLException { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setAccessUrl(new URL("https://localhost:8080/")); + desc.setPassword("Random Password"); + + final var artifact = factory.create(desc); + + /* ACT */ + final var result = factory.update(artifact, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ArtifactTests.java b/src/test/java/io/dataspaceconnector/model/ArtifactTests.java new file mode 100644 index 000000000..de7e5e802 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ArtifactTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ArtifactTests { + + @Test + public void randomTests() { + ArtifactDesc desc = new ArtifactDesc(); + ArtifactFactory factory = new ArtifactFactory(); + var artifact = factory.create(desc); + + artifact.incrementAccessCounter(); + + assertEquals(1, artifact.getNumAccessed()); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/CatalogFactoryTest.java b/src/test/java/io/dataspaceconnector/model/CatalogFactoryTest.java new file mode 100644 index 000000000..69c3fac10 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/CatalogFactoryTest.java @@ -0,0 +1,325 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CatalogFactoryTest { + + private CatalogFactory factory; + + @BeforeEach + public void init() { + this.factory = new CatalogFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", CatalogFactory.DEFAULT_TITLE); + } + + @Test + public void default_description_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", CatalogFactory.DEFAULT_DESCRIPTION); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new CatalogDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new CatalogDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new CatalogDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_offeredResourcesEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new CatalogDesc()); + + /* ASSERT */ + assertEquals(0, result.getOfferedResources().size()); + } + + @Test + public void create_validDesc_requestedResourcesEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new CatalogDesc()); + + /* ASSERT */ + assertEquals(0, result.getRequestedResources().size()); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(CatalogFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setTitle("Random Title"); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + factory.update(catalog, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), catalog.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setTitle("Random Title"); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, new CatalogDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * desc. + */ + + @Test + public void create_nullDescription_defaultDescription() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setDescription(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(CatalogFactory.DEFAULT_DESCRIPTION, result.getDescription()); + } + + @Test + public void update_differentDescription_setDescription() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setDescription("Random Desc"); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + factory.update(catalog, desc); + + /* ASSERT */ + assertEquals(desc.getDescription(), catalog.getDescription()); + } + + @Test + public void update_differentDescription_returnTrue() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setDescription("Random Desc"); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameDescription_returnFalse() { + /* ARRANGE */ + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, new CatalogDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + factory.update(catalog, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), catalog.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new CatalogDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var catalog = factory.create(new CatalogDesc()); + + /* ACT */ + final var result = factory.update(catalog, new CatalogDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullCatalog_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new CatalogDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var catalog = factory.create(new CatalogDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(catalog, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ContractFactoryTest.java b/src/test/java/io/dataspaceconnector/model/ContractFactoryTest.java new file mode 100644 index 000000000..737671891 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ContractFactoryTest.java @@ -0,0 +1,575 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ContractFactoryTest { + + private ContractFactory factory; + + @BeforeEach + public void init() { + this.factory = new ContractFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ContractFactory.DEFAULT_TITLE); + } + + @Test + public void default_remoteId_is_genesis() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create("genesis"), ContractFactory.DEFAULT_REMOTE_ID); + } + + @Test + public void default_consumer_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ContractFactory.DEFAULT_CONSUMER); + } + + @Test + public void default_provider_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ContractFactory.DEFAULT_PROVIDER); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_rulesEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ContractDesc()); + + /* ASSERT */ + assertEquals(0, result.getRules().size()); + } + + @Test + public void create_validDesc_resourcesEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ContractDesc()); + + /* ASSERT */ + assertEquals(0, result.getResources().size()); + } + + /** + * remoteId. + */ + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + + final var contract = factory.create(desc); + + desc.setRemoteId(URI.create("uri")); + + /* ACT */ + factory.update(contract, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), contract.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setRemoteId(URI.create("uri")); + + final var contract = factory.create(desc); + + desc.setRemoteId(URI.create("differentUri")); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * consumer. + */ + + @Test + public void create_nullConsumer_defaultConsumer() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setConsumer(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractFactory.DEFAULT_CONSUMER, result.getConsumer()); + } + + @Test + public void update_differentConsumer_setConsumer() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setConsumer(URI.create("uri")); + + /* ACT */ + factory.update(contract, desc); + + /* ASSERT */ + assertEquals(desc.getConsumer(), contract.getConsumer()); + } + + @Test + public void update_differentConsumer_returnTrue() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setConsumer(URI.create("uri")); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameConsumer_returnFalse() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setConsumer(URI.create("consumer")); + final var contract = factory.create(desc); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * provider. + */ + + @Test + public void create_nullProvider_defaultProvider() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setProvider(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractFactory.DEFAULT_PROVIDER, result.getProvider()); + } + + @Test + public void update_differentProvider_setProvider() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setProvider(URI.create("uri")); + + + /* ACT */ + factory.update(contract, desc); + + /* ASSERT */ + assertEquals(desc.getProvider(), contract.getProvider()); + } + + @Test + public void update_differentProvider_returnTrue() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setProvider(URI.create("uri")); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameProvider_returnFalse() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setProvider(URI.create("randomProvider")); + final var contract = factory.create(desc); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setTitle("Random Title"); + + /* ACT */ + factory.update(contract, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), contract.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setTitle("Random Title"); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setTitle("Random Title"); + final var contract = factory.create(desc); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * start. + */ + +// @Test +// public void create_nullStart_defaultStart() { +// /* ARRANGE */ +// final var desc = new ContractDesc(); +// desc.setStart(null); +// +// final var now = ZonedDateTime.now(ZoneOffset.UTC); +// +// /* ACT */ +// final var result = factory.create(desc); +// +// /* ASSERT */ +// assertTrue(now.before(result.getStart())); +// } +// +// @Test +// public void update_differentStart_setStart() { +// /* ARRANGE */ +// final var desc = new ContractDesc(); +// desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); +// +// final var contract = factory.create(new ContractDesc()); +// +// /* ACT */ +// factory.update(contract, desc); +// +// /* ASSERT */ +// assertEquals(desc.getStart(), contract.getStart()); +// } +// +// @Test +// public void update_differentStart_returnTrue() throws ParseException { +// /* ARRANGE */ +// final var initialStartTime = ZonedDateTime.now(ZoneOffset.UTC); +// final var initialEndTime = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").parse("20-Feb-2021 10:10:10"); +// +// final var initDesc = new ContractDesc(); +// initDesc.setStart(initialStartTime); +// initDesc.setEnd(initialEndTime); +// +// final var contract = factory.create(initDesc); +// +// final var desc = new ContractDesc(); +// desc.setStart(new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").parse("15-Feb-2021 10:10:10")); +// desc.setEnd(initialEndTime); +// +// /* ACT */ +// final var result = factory.update(contract, desc); +// +// /* ASSERT */ +// assertTrue(result); +// } +// +// @Test +// public void update_sameStart_returnFalse() { +// /* ARRANGE */ +// final var contract = factory.create(new ContractDesc()); +// +// /* ACT */ +// final var result = factory.update(contract, new ContractDesc()); +// +// /* ASSERT */ +// assertFalse(result); +// } + + /** + * additional. + */ + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setAdditional(Map.of("Y", "X")); + + /* ACT */ + factory.update(contract, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), contract.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + desc.setAdditional(Map.of("Y", "X")); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var desc = new ContractDesc(); + desc.setStart(ZonedDateTime.now(ZoneOffset.UTC)); + desc.setEnd(ZonedDateTime.now(ZoneOffset.UTC)); + final var contract = factory.create(desc); + + /* ACT */ + final var result = factory.update(contract, desc); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullContract_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new ContractDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contract = factory.create(new ContractDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contract, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ContractRuleFactoryTest.java b/src/test/java/io/dataspaceconnector/model/ContractRuleFactoryTest.java new file mode 100644 index 000000000..9563b9b39 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ContractRuleFactoryTest.java @@ -0,0 +1,382 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ContractRuleFactoryTest { + + private ContractRuleFactory factory; + + @BeforeEach + public void init() { + this.factory = new ContractRuleFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ContractRuleFactory.DEFAULT_TITLE); + } + + @Test + public void default_remoteId_is_genesis() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create("genesis"), ContractRuleFactory.DEFAULT_REMOTE_ID); + } + + @Test + public void default_rule_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ContractRuleFactory.DEFAULT_RULE); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractRuleDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractRuleDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new ContractRuleDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_contractsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new ContractRuleDesc()); + + /* ASSERT */ + assertEquals(0, result.getContracts().size()); + } + + /** + * remoteId. + */ + + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractRuleFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setRemoteId(URI.create("uri")); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + factory.update(contractRule, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), contractRule.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setRemoteId(URI.create("uri")); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, new ContractRuleDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractRuleFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setTitle("Random Title"); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + factory.update(contractRule, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), contractRule.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setTitle("Random Title"); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, new ContractRuleDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * rule. + */ + + @Test + public void create_nullRule_defaultRule() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setValue(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ContractRuleFactory.DEFAULT_RULE, result.getValue()); + } + + @Test + public void update_differentRule_setRule() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setValue("Random Rule"); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + factory.update(contractRule, desc); + + /* ASSERT */ + assertEquals(desc.getValue(), contractRule.getValue()); + } + + @Test + public void update_differentRule_returnTrue() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setValue("Random Rule"); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRule_returnFalse() { + /* ARRANGE */ + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, new ContractRuleDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + factory.update(contractRule, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), contractRule.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT */ + final var result = factory.update(contractRule, new ContractRuleDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullContractRule_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new ContractRuleDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contractRule = factory.create(new ContractRuleDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contractRule, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/OfferedResourceFactoryTest.java b/src/test/java/io/dataspaceconnector/model/OfferedResourceFactoryTest.java new file mode 100644 index 000000000..3fa59278f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/OfferedResourceFactoryTest.java @@ -0,0 +1,813 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OfferedResourceFactoryTest { + + private OfferedResourceFactory factory; + + @BeforeEach + public void init() { + this.factory = new OfferedResourceFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_TITLE); + } + + @Test + public void default_description_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_DESCRIPTION); + } + + @Test + public void default_keywords_is_DSC() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(Collections.singletonList("DSC"), ResourceFactory.DEFAULT_KEYWORDS); + } + + @Test + public void default_publisher_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_PUBLISHER); + } + + @Test + public void default_language_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_LANGUAGE); + } + + @Test + public void default_licence_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_LICENCE); + } + + @Test + public void default_sovereign_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_SOVEREIGN); + } + + @Test + public void default_endpointDocumentation_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_ENDPOINT_DOCS); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_representationsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getRepresentations().size()); + } + + @Test + public void create_validDesc_contractsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getContracts().size()); + } + + @Test + public void create_validDesc_keywordsDefault() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(1, result.getKeywords().size()); + } + + @Test + public void create_validDesc_catalogsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getCatalogs().size()); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setTitle("Random Title"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), resource.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setTitle("Random Title"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * description. + */ + + @Test + public void create_nullDescription_defaultDescription() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setDescription(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_DESCRIPTION, result.getDescription()); + } + + @Test + public void update_differentDescription_setDescription() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setDescription("Random Description"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getDescription(), resource.getDescription()); + } + + @Test + public void update_differentDescription_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setDescription("Random Description"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameDescription_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * keywords. + */ + + @Test + public void create_nullKeywords_defaultKeywords() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setKeywords(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertTrue(ResourceFactory.DEFAULT_KEYWORDS.containsAll(result.getKeywords())); + assertEquals(ResourceFactory.DEFAULT_KEYWORDS.size(), result.getKeywords().size()); + } + + @Test + public void update_differentKeywords_setKeywords() { + /* ARRANGE */ + final var keywords = new ArrayList(); + keywords.add("Default"); + keywords.add("Key"); + + final var desc = new OfferedResourceDesc(); + desc.setKeywords(keywords); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getKeywords(), resource.getKeywords()); + } + + @Test + public void update_differentKeywords_returnTrue() { + /* ARRANGE */ + final var keywords = new ArrayList(); + keywords.add("Default"); + keywords.add("Key"); + + final var desc = new OfferedResourceDesc(); + desc.setKeywords(keywords); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameKeywords_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * publisher. + */ + + @Test + public void create_nullPublisher_defaultPublisher() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setPublisher(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_PUBLISHER, result.getPublisher()); + } + + @Test + public void update_differentPublisher_setPublisher() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setPublisher(URI.create("RandomPublisher")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getPublisher(), resource.getPublisher()); + } + + @Test + public void update_differentPublisher_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setPublisher(URI.create("RandomPublisher")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_samePublisher_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + + /** + * language. + */ + + @Test + public void create_nullLanguage_defaultLanguage() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLanguage(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_DESCRIPTION, result.getLanguage()); + } + + @Test + public void update_differentLanguage_setLanguage() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLanguage("Random Language"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getLanguage(), resource.getLanguage()); + } + + @Test + public void update_differentLanguage_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLanguage("Random Language"); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameLanguage_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * licence. + */ + + @Test + public void create_nullLicence_defaultLicence() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLicence(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_LICENCE, result.getLicence()); + } + + @Test + public void update_differentLicence_setLicence() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLicence(URI.create("RandomLicence")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getLicence(), resource.getLicence()); + } + + @Test + public void update_differentLicence_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setLicence(URI.create("RandomLicence")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameLicence_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * sovereign. + */ + + @Test + public void create_nullSovereign_defaultSovereign() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setSovereign(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_SOVEREIGN, result.getSovereign()); + } + + @Test + public void update_differentSovereign_setSovereign() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setSovereign(URI.create("RandomSovereign")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getSovereign(), resource.getSovereign()); + } + + @Test + public void update_differentSovereign_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setSovereign(URI.create("RandomSovereign")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameSovereign_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * endpointDocumentation. + */ + + @Test + public void create_nullEndpointDocumentation_defaultEndpointDocumentation() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setEndpointDocumentation(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_ENDPOINT_DOCS, result.getEndpointDocumentation()); + } + + @Test + public void update_differentEndpointDocumentation_setEndpointDocumentation() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setEndpointDocumentation(URI.create("RandomEndpointDocumentation")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getEndpointDocumentation(), resource.getEndpointDocumentation()); + } + + @Test + public void update_differentEndpointDocumentation_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setEndpointDocumentation(URI.create("RandomEndpointDocumentation")); + + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameEndpointDocumentation_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), representation.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new OfferedResourceDesc()); + + /* ACT */ + final var result = factory.update(representation, new OfferedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * version + */ + + @Test + public void create_version_is_1() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(1, result.getVersion()); + } + + @Test + public void update_same_sameVersion() { + /* ARRANGE */ + var resource = factory.create(new OfferedResourceDesc()); + + /* ACT */ + factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(1, resource.getVersion()); + } + + @Test + public void update_different_incrementVersion() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + desc.setTitle("RANDOM TITLE"); + + var resource = factory.create(desc); + + /* ACT */ + factory.update(resource, new OfferedResourceDesc()); + + /* ASSERT */ + assertEquals(2, resource.getVersion()); + } + + /** + * update inputs. + */ + + @Test + public void update_nullOfferedResource_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new OfferedResourceDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contract = factory.create(new OfferedResourceDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contract, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/RepresentationDescTest.java b/src/test/java/io/dataspaceconnector/model/RepresentationDescTest.java new file mode 100644 index 000000000..a6f490653 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/RepresentationDescTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//package io.dataspaceconnector.model; +// +//import static org.junit.jupiter.api.Assertions.*; +// +//import org.junit.jupiter.api.Test; +// +//import nl.jqno.equalsverifier.EqualsVerifier; +// +//class RepresentationDescTest { +// @Test +// public void equalsRepresentationDesc() { +// EqualsVerifier +// .forClasses(AbstractDescription.class, AgreementDesc.class, ArtifactDesc.class, +// CatalogDesc.class, ContractDesc.class, ContractRuleDesc.class, +// OfferedResourceDesc.class, RepresentationDesc.class, +// RequestedResourceDesc.class, ResourceDesc.class) +// .verify(); +// } +//} diff --git a/src/test/java/io/dataspaceconnector/model/RepresentationFactoryTest.java b/src/test/java/io/dataspaceconnector/model/RepresentationFactoryTest.java new file mode 100644 index 000000000..37d85d966 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/RepresentationFactoryTest.java @@ -0,0 +1,530 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RepresentationFactoryTest { + + private RepresentationFactory factory; + + @BeforeEach + public void init() { + this.factory = new RepresentationFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", RepresentationFactory.DEFAULT_TITLE); + } + + @Test + public void default_remoteId_is_genesis() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create("genesis"), RepresentationFactory.DEFAULT_REMOTE_ID); + } + + @Test + public void default_language_is_EN() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("EN", RepresentationFactory.DEFAULT_LANGUAGE); + } + + @Test + public void default_mediaType_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", RepresentationFactory.DEFAULT_MEDIA_TYPE); + } + + @Test + public void default_standard_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", RepresentationFactory.DEFAULT_STANDARD); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RepresentationDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RepresentationDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RepresentationDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_artifactsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RepresentationDesc()); + + /* ASSERT */ + assertEquals(0, result.getArtifacts().size()); + } + + @Test + public void create_validDesc_resourcesEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RepresentationDesc()); + + /* ASSERT */ + assertEquals(0, result.getResources().size()); + } + + /** + * remoteId. + */ + + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RepresentationFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setRemoteId(URI.create("uri")); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), representation.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setRemoteId(URI.create("uri")); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RepresentationFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setTitle("Random Title"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), representation.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setTitle("Random Title"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * mediaType. + */ + + @Test + public void create_nullMediaType_defaultMediaType() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setMediaType(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RepresentationFactory.DEFAULT_MEDIA_TYPE, result.getMediaType()); + } + + @Test + public void update_differentMediaType_setMediaType() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setMediaType("Random MediaType"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getMediaType(), representation.getMediaType()); + } + + @Test + public void update_differentMediaType_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setMediaType("Random MediaType"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameMediaType_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * language. + */ + + @Test + public void create_nullLanguage_defaultLanguage() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setLanguage(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RepresentationFactory.DEFAULT_LANGUAGE, result.getLanguage()); + } + + @Test + public void update_differentLanguage_setLanguage() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setLanguage("Random Language"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getLanguage(), representation.getLanguage()); + } + + @Test + public void update_differentLanguage_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setLanguage("Random Language"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameLanguage_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * standard. + */ + + @Test + public void create_nullStandard_defaultStandard() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setStandard(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RepresentationFactory.DEFAULT_STANDARD, result.getStandard()); + } + + @Test + public void update_differentStandard_setStandard() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setStandard("Random Standard"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getStandard(), representation.getStandard()); + } + + @Test + public void update_differentStandard_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setStandard("Random Standard"); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameStandard_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), representation.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new RepresentationDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RepresentationDesc()); + + /* ACT */ + final var result = factory.update(representation, new RepresentationDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullRepresentation_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new RepresentationDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contract = factory.create(new RepresentationDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contract, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/RequestedResourceFactoryTest.java b/src/test/java/io/dataspaceconnector/model/RequestedResourceFactoryTest.java new file mode 100644 index 000000000..463c75ff6 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/RequestedResourceFactoryTest.java @@ -0,0 +1,806 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestedResourceFactoryTest { + + private RequestedResourceFactory factory; + + @BeforeEach + public void init() { + this.factory = new RequestedResourceFactory(); + } + + @Test + public void default_title_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_TITLE); + } + + @Test + public void default_description_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_DESCRIPTION); + } + + @Test + public void default_keywords_is_DSC() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(Collections.singletonList("DSC"), ResourceFactory.DEFAULT_KEYWORDS); + } + + @Test + public void default_publisher_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_PUBLISHER); + } + + @Test + public void default_language_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals("", ResourceFactory.DEFAULT_LANGUAGE); + } + + @Test + public void default_licence_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_LICENCE); + } + + @Test + public void default_sovereign_is_empty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(URI.create(""), ResourceFactory.DEFAULT_SOVEREIGN); + } + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.create(null)); + } + + @Test + public void create_validDesc_creationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertNull(result.getCreationDate()); + } + + @Test + public void create_validDesc_modificationDateNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertNull(result.getModificationDate()); + } + + @Test + public void create_validDesc_idNull() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertNull(result.getId()); + } + + @Test + public void create_validDesc_representationsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getRepresentations().size()); + } + + @Test + public void create_validDesc_contractsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getContracts().size()); + } + + @Test + public void create_validDesc_keywordsDefault() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(1, result.getKeywords().size()); + } + + @Test + public void create_validDesc_catalogsEmpty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(0, result.getCatalogs().size()); + } + + /** + * title. + */ + + @Test + public void create_nullTitle_defaultTitle() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setTitle(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_TITLE, result.getTitle()); + } + + @Test + public void update_differentTitle_setTitle() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setTitle("Random Title"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getTitle(), resource.getTitle()); + } + + @Test + public void update_differentTitle_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setTitle("Random Title"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameTitle_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * description. + */ + + @Test + public void create_nullDescription_defaultDescription() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setDescription(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_DESCRIPTION, result.getDescription()); + } + + @Test + public void update_differentDescription_setDescription() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setDescription("Random Description"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getDescription(), resource.getDescription()); + } + + @Test + public void update_differentDescription_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setDescription("Random Description"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameDescription_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * keywords. + */ + + @Test + public void create_nullKeywords_defaultKeywords() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setKeywords(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertTrue(ResourceFactory.DEFAULT_KEYWORDS.containsAll(result.getKeywords())); + assertEquals(ResourceFactory.DEFAULT_KEYWORDS.size(), result.getKeywords().size()); + } + + @Test + public void update_differentKeywords_setKeywords() { + /* ARRANGE */ + final var keywords = new ArrayList(); + keywords.add("Default"); + keywords.add("Key"); + + final var desc = new RequestedResourceDesc(); + desc.setKeywords(keywords); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getKeywords(), resource.getKeywords()); + } + + @Test + public void update_differentKeywords_returnTrue() { + /* ARRANGE */ + final var keywords = new ArrayList(); + keywords.add("Default"); + keywords.add("Key"); + + final var desc = new RequestedResourceDesc(); + desc.setKeywords(keywords); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameKeywords_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * publisher. + */ + + @Test + public void create_nullPublisher_defaultPublisher() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setPublisher(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_PUBLISHER, result.getPublisher()); + } + + @Test + public void update_differentPublisher_setPublisher() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setPublisher(URI.create("RandomPublisher")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getPublisher(), resource.getPublisher()); + } + + @Test + public void update_differentPublisher_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setPublisher(URI.create("RandomPublisher")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_samePublisher_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + + /** + * language. + */ + + @Test + public void create_nullLanguage_defaultLanguage() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLanguage(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_DESCRIPTION, result.getLanguage()); + } + + @Test + public void update_differentLanguage_setLanguage() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLanguage("Random Language"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getLanguage(), resource.getLanguage()); + } + + @Test + public void update_differentLanguage_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLanguage("Random Language"); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameLanguage_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * licence. + */ + + @Test + public void create_nullLicence_defaultLicence() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLicence(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_PUBLISHER, result.getLicence()); + } + + @Test + public void update_differentLicence_setLicence() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLicence(URI.create("RandomLicence")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getLicence(), resource.getLicence()); + } + + @Test + public void update_differentLicence_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setLicence(URI.create("RandomLicence")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameLicence_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + + /** + * sovereign. + */ + + @Test + public void create_nullSovereign_defaultSovereign() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setSovereign(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(ResourceFactory.DEFAULT_PUBLISHER, result.getSovereign()); + } + + @Test + public void update_differentSovereign_setSovereign() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setSovereign(URI.create("RandomSovereign")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getSovereign(), resource.getSovereign()); + } + + @Test + public void update_differentSovereign_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setSovereign(URI.create("RandomSovereign")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameSovereign_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * additional. + */ + + @Test + public void create_nullAdditional_defaultAdditional() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setAdditional(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(new HashMap<>(), result.getAdditional()); + } + + @Test + public void update_differentAdditional_setAdditional() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(representation, desc); + + /* ASSERT */ + assertEquals(desc.getAdditional(), representation.getAdditional()); + } + + @Test + public void update_differentAdditional_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setAdditional(Map.of("Y", "X")); + + final var representation = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(representation, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameAdditional_returnFalse() { + /* ARRANGE */ + final var representation = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(representation, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * version + */ + + @Test + public void create_version_is_1() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = factory.create(new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(1, result.getVersion()); + } + + @Test + public void update_same_sameVersion() { + /* ARRANGE */ + var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(1, resource.getVersion()); + } + + @Test + public void update_different_incrementVersion() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setTitle("RANDOM TITLE"); + + var resource = factory.create(desc); + + /* ACT */ + factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + assertEquals(2, resource.getVersion()); + } + + + /** + * remoteId. + */ + + @Test + public void create_nullRemoteId_defaultRemoteId() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setRemoteId(null); + + /* ACT */ + final var result = factory.create(desc); + + /* ASSERT */ + assertEquals(RequestedResourceFactory.DEFAULT_REMOTE_ID, result.getRemoteId()); + } + + @Test + public void update_differentRemoteId_setRemoteId() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setRemoteId(URI.create("RandomRemoteId")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + factory.update(resource, desc); + + /* ASSERT */ + assertEquals(desc.getRemoteId(), resource.getRemoteId()); + } + + @Test + public void update_differentRemoteId_returnTrue() { + /* ARRANGE */ + final var desc = new RequestedResourceDesc(); + desc.setRemoteId(URI.create("RandomRemoteId")); + + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, desc); + + /* ASSERT */ + Assertions.assertTrue(result); + } + + @Test + public void update_sameRemoteId_returnFalse() { + /* ARRANGE */ + final var resource = factory.create(new RequestedResourceDesc()); + + /* ACT */ + final var result = factory.update(resource, new RequestedResourceDesc()); + + /* ASSERT */ + Assertions.assertFalse(result); + } + + /** + * update inputs. + */ + + @Test + public void update_nullRequestedResource_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(null, new RequestedResourceDesc())); + } + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + final var contract = factory.create(new RequestedResourceDesc()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> factory.update(contract, null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/ResourceTest.java b/src/test/java/io/dataspaceconnector/model/ResourceTest.java new file mode 100644 index 000000000..ed0d75c3c --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/ResourceTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model; + +import java.util.ArrayList; + +import io.dataspaceconnector.exceptions.ResourceException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ResourceTest { + @Test + public void setCatalogs_anything_throwsResourceException() { + /* ARRANGE */ + final var resource = new Resource(); + + /* ACT && ASSERT */ + assertThrows(ResourceException.class, () -> resource.setCatalogs(new ArrayList<>())); + } + + @Test + public void getCatalogs_nothing_throwsResourceException() { + /* ARRANGE */ + final var resource = new Resource(); + + /* ACT && ASSERT */ + assertThrows(ResourceException.class, resource::getCatalogs); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDescTest.java new file mode 100644 index 000000000..57f0d162f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/ArtifactRequestMessageDescTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ArtifactRequestMessageDescTest { + + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new ArtifactRequestMessageDesc(); + + /* ASSERT */ + assertNull(result.getRequestedArtifact()); + assertNull(result.getTransferContract()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var artifact = URI.create("someArtifact"); + final var contract = URI.create("someContract"); + + /* ACT */ + final var result = new ArtifactRequestMessageDesc(recipient, artifact, contract); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(artifact, result.getRequestedArtifact()); + assertEquals(contract, result.getTransferContract()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(ArtifactRequestMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDescTest.java new file mode 100644 index 000000000..b540a28ac --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/ArtifactResponseMessageDescTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ArtifactResponseMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new ArtifactResponseMessageDesc(); + + /* ASSERT */ + assertNull(result.getRecipient()); + assertNull(result.getCorrelationMessage()); + assertNull(result.getTransferContract()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var message = URI.create("someMessage"); + final var contract = URI.create("someContract"); + + /* ACT */ + final var result = new ArtifactResponseMessageDesc(recipient, message, contract); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(message, result.getCorrelationMessage()); + assertEquals(contract, result.getTransferContract()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(ArtifactResponseMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDescTest.java new file mode 100644 index 000000000..4e94ef40d --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/ContractAgreementMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ContractAgreementMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new ContractAgreementMessageDesc(); + + /* ASSERT */ + assertNull(result.getCorrelationMessage()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var message = URI.create("someMessage"); + + /* ACT */ + final var result = new ContractAgreementMessageDesc(recipient, message); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(message, result.getCorrelationMessage()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(ContractAgreementMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDescTest.java new file mode 100644 index 000000000..8eaf2ef6f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/ContractRejectionMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ContractRejectionMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new ContractRejectionMessageDesc(); + + /* ASSERT */ + assertNull(result.getCorrelationMessage()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var message = URI.create("someMessage"); + + /* ACT */ + final var result = new ContractRejectionMessageDesc(recipient, message); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(message, result.getCorrelationMessage()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(ContractRejectionMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/ContractRequestMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/ContractRequestMessageDescTest.java new file mode 100644 index 000000000..f500fa6e2 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/ContractRequestMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ContractRequestMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new ContractRequestMessageDesc(); + + /* ASSERT */ + assertNull(result.getTransferContract()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var contract = URI.create("someContract"); + + /* ACT */ + final var result = new ContractRequestMessageDesc(recipient, contract); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(contract, result.getTransferContract()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(ContractRequestMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDescTest.java new file mode 100644 index 000000000..3167d3fd6 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/DescriptionRequestMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class DescriptionRequestMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new DescriptionRequestMessageDesc(); + + /* ASSERT */ + assertNull(result.getRequestedElement()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var element = URI.create("someElement"); + + /* ACT */ + final var result = new DescriptionRequestMessageDesc(recipient, element); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(element, result.getRequestedElement()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(DescriptionRequestMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDescTest.java new file mode 100644 index 000000000..f6fa84dac --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/DescriptionResponseMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class DescriptionResponseMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new DescriptionResponseMessageDesc(); + + /* ASSERT */ + assertNull(result.getCorrelationMessage()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var message = URI.create("someMessage"); + + /* ACT */ + final var result = new DescriptionResponseMessageDesc(recipient, message); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(message, result.getCorrelationMessage()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(DescriptionResponseMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/LogMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/LogMessageDescTest.java new file mode 100644 index 000000000..b24dee70b --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/LogMessageDescTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LogMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new LogMessageDesc(); + + /* ASSERT */ + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + + /* ACT */ + final var result = new LogMessageDesc(recipient); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(LogMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/MessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/MessageDescTest.java new file mode 100644 index 000000000..3e71b2c94 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/MessageDescTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new MessageDesc(); + + /* ASSERT */ + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + + /* ACT */ + final var result = new MessageDesc(recipient); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(MessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDescTest.java new file mode 100644 index 000000000..3008d0b32 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/MessageProcessedNotificationMessageDescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MessageProcessedNotificationMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new MessageProcessedNotificationMessageDesc(); + + /* ASSERT */ + assertNull(result.getCorrelationMessage()); + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + final var message = URI.create("someMessage"); + + /* ACT */ + final var result = new MessageProcessedNotificationMessageDesc(recipient, message); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + assertEquals(message, result.getCorrelationMessage()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(MessageProcessedNotificationMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/messages/NotificationMessageDescTest.java b/src/test/java/io/dataspaceconnector/model/messages/NotificationMessageDescTest.java new file mode 100644 index 000000000..78f98a8c6 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/messages/NotificationMessageDescTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.messages; + +import java.net.URI; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class NotificationMessageDescTest { + @Test + public void defaultConstructor_nothing_emptyDesc() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = new NotificationMessageDesc(); + + /* ASSERT */ + assertNull(result.getRecipient()); + } + + @Test + public void allArgsConstructor_valid_validDesc() { + /* ARRANGE */ + final var recipient = URI.create("someRecipient"); + + /* ACT */ + final var result = new NotificationMessageDesc(recipient); + + /* ASSERT */ + assertEquals(recipient, result.getRecipient()); + } + + @Test + public void equals_everything_valid() { + EqualsVerifier.simple().forClass(NotificationMessageDesc.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/model/templates/ArtifactTemplateTest.java b/src/test/java/io/dataspaceconnector/model/templates/ArtifactTemplateTest.java new file mode 100644 index 000000000..60eef97f0 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/model/templates/ArtifactTemplateTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.model.templates; + + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +public class ArtifactTemplateTest { + + @Test + public void equals_verify() { + EqualsVerifier.simple().forClass(ArtifactTemplate.class).verify(); + } +} diff --git a/src/test/java/io/dataspaceconnector/repositories/RuleRepositoryIT.java b/src/test/java/io/dataspaceconnector/repositories/RuleRepositoryIT.java new file mode 100644 index 000000000..28bd49df9 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/repositories/RuleRepositoryIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.repositories; + +import io.dataspaceconnector.model.ContractRule; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@SpringBootTest +class RuleRepositoryIT { + + @Autowired + private RuleRepository repository; + + @Test + public void rule_can_hold_min_1_gb() { + /* ARRANGE */ + final var rule = get1GBRule(); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> repository.saveAndFlush(rule)); + } + + // NOTE This needs to be tested with the postgres db. + + @SneakyThrows + private ContractRule get1GBRule() { + /* ARRANGE */ + final var builder = new StringBuilder(); + + // Java chars are 2 bytes big (ref https://stackoverflow.com/questions/2474486/create-a-java-variable-string-of-a-specific-size-mbs) + // For 1GB big array (ref https://www.postgresql.org/docs/12/limits.html) + // 1 GB = 1073741824 Bytes -> We need 536870912 Chars + + for(long i = 0L; i < 536870912; i++) + builder.append("0"); + + final var constructor = ContractRule.class.getConstructor(); + constructor.setAccessible(true); + final var rule = constructor.newInstance(); + + final var valueField = rule.getClass().getDeclaredField("value"); + valueField.setAccessible(true); + valueField.set(rule, builder.toString()); + + return rule; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/EntityResolverTest.java b/src/test/java/io/dataspaceconnector/services/EntityResolverTest.java new file mode 100644 index 000000000..3c3817764 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/EntityResolverTest.java @@ -0,0 +1,285 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import java.net.URI; +import java.util.UUID; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactImpl; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.services.ids.builder.IdsCatalogBuilder; +import io.dataspaceconnector.services.ids.builder.IdsContractBuilder; +import io.dataspaceconnector.services.ids.builder.IdsRepresentationBuilder; +import io.dataspaceconnector.services.ids.builder.IdsResourceBuilder; +import io.dataspaceconnector.services.resources.AgreementService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.CatalogService; +import io.dataspaceconnector.services.resources.ContractService; +import io.dataspaceconnector.services.resources.RepresentationService; +import io.dataspaceconnector.services.resources.ResourceService; +import io.dataspaceconnector.services.resources.RuleService; +import io.dataspaceconnector.services.usagecontrol.AllowAccessVerifier; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {EntityResolver.class}) +public class EntityResolverTest { + + @MockBean + private ArtifactService artifactService; + + @MockBean + private RepresentationService representationService; + + @MockBean + private ResourceService offerService; + + @MockBean + private CatalogService catalogService; + + @MockBean + private ContractService contractService; + + @MockBean + private RuleService ruleService; + + @MockBean + private AgreementService agreementService; + + @MockBean + private IdsCatalogBuilder catalogBuilder; + + @MockBean + private IdsResourceBuilder offerBuilder; + + @MockBean + private IdsArtifactBuilder artifactBuilder; + + @MockBean + private IdsRepresentationBuilder representationBuilder; + + @MockBean + private IdsContractBuilder contractBuilder; + + @MockBean + private AllowAccessVerifier allowAccessVerifier; + + @MockBean + private BlockingArtifactReceiver artifactReceiver; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private EntityResolver resolver; + + private UUID resourceId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + @Test + public void getEntityById_null_throwsResourceNotFoundException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> resolver.getEntityById(null)); + } + + @Test + public void getEntityById_validArtifact_returnArtifact() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/artifacts/" + resourceId); + final var resource = getArtifact(); + Mockito.doReturn(resource).when(artifactService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validRepresentation_returnRepresentation() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/representations/" + resourceId); + final var resource = getRepresentation(); + Mockito.doReturn(resource).when(representationService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validOfferedResource_returnOfferedResource() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/offers/" + resourceId); + final var resource = getOfferedResource(); + Mockito.doReturn(resource).when(offerService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validCatalog_returnCatalog() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/catalogs/" + resourceId); + final var resource = getCatalog(); + Mockito.doReturn(resource).when(catalogService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validContract_returnContract() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/contracts/" + resourceId); + final var resource = getContract(); + Mockito.doReturn(resource).when(contractService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validRule_returnRule() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/rules/" + resourceId); + final var resource = getRule(); + Mockito.doReturn(resource).when(ruleService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_validAgreement_returnAgreement() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/api/agreements/" + resourceId); + final var resource = getAgreement(); + Mockito.doReturn(resource).when(agreementService).get(resourceId); + + /* ACT */ + final var result = resolver.getEntityById(resourceUri); + + /* ASSERT */ + assertEquals(resource, result); + } + + @Test + public void getEntityById_malformedAgreement_() { + /* ARRANGE */ + final var resourceUri = URI.create("https://localhost:8080/someWhereIdontKnow/" + resourceId); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, () -> resolver.getEntityById(resourceUri)); + } + + /** + * Utilities + */ + + private Artifact getArtifact() { + final var output = new ArtifactImpl(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + private Representation getRepresentation() { + final var output = new Representation(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + @SneakyThrows + private OfferedResource getOfferedResource() { + final var constructor = OfferedResource.class.getDeclaredConstructor(); + constructor.setAccessible(true); + final var output = constructor.newInstance(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + @SneakyThrows + private Catalog getCatalog() { + final var constructor = Catalog.class.getDeclaredConstructor(); + constructor.setAccessible(true); + final var output = constructor.newInstance(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + @SneakyThrows + private Contract getContract() { + final var constructor = Contract.class.getDeclaredConstructor(); + constructor.setAccessible(true); + final var output = constructor.newInstance(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + @SneakyThrows + private ContractRule getRule() { + final var constructor = ContractRule.class.getDeclaredConstructor(); + constructor.setAccessible(true); + final var output = constructor.newInstance(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } + + @SneakyThrows + private Agreement getAgreement() { + final var constructor = Agreement.class.getDeclaredConstructor(); + constructor.setAccessible(true); + final var output = constructor.newInstance(); + ReflectionTestUtils.setField(output, "id", resourceId); + return output; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/HttpServiceTest.java b/src/test/java/io/dataspaceconnector/services/HttpServiceTest.java new file mode 100644 index 000000000..01e950607 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/HttpServiceTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import io.dataspaceconnector.model.QueryInput; +import kotlin.Pair; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.bouncycastle.util.Arrays; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {HttpService.class}) +class HttpServiceTest { + + @MockBean + de.fraunhofer.isst.ids.framework.communication.http.HttpService httpSvc; + + @Autowired + HttpService service; + + @Test + public void toArgs_null_emptyArgs() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = service.toArgs(null); + + /* ASSERT */ + assertEquals(new HttpService.HttpArgs(), result); + } + + @Test + public void toArgs_inputSetFields_ArgsSetFields() { + /* ARRANGE */ + final var params = Map.of("A", "AV", "B", "BV"); + final var headers = Map.of("C", "CV", "D", "DV"); + + final var input = new QueryInput(); + input.setParams(params); + input.setHeaders(headers); + + final var expected = new HttpService.HttpArgs(); + expected.setParams(params); + expected.setHeaders(headers); + + /* ACT */ + final var result = service.toArgs(input); + + /* ASSERT */ + assertEquals(expected, result); + } + + @Test + public void toArgs_nullAuth_emptyArgs() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = service.toArgs(null, null); + + /* ASSERT */ + assertEquals(new HttpService.HttpArgs(), result); + } + + @Test + public void toArgs_inputSetAuth_ArgsSetAuthField() { + /* ARRANGE */ + final var params = Map.of("A", "AV", "B", "BV"); + final var headers = Map.of("C", "CV", "D", "DV"); + final var auth = new Pair("X", "Y"); + + final var input = new QueryInput(); + input.setParams(params); + input.setHeaders(headers); + + final var expected = new HttpService.HttpArgs(); + expected.setParams(params); + expected.setHeaders(headers); + expected.setAuth(auth); + + /* ACT */ + final var result = service.toArgs(input, auth); + + /* ASSERT */ + assertEquals(expected, result); + } + + @Test + public void request_get_equalsToGetMethod() throws IOException, URISyntaxException { + /* ARRANGE */ + final var target = new URL("https://someTarget"); + final var args = new HttpService.HttpArgs(); + + final var response = + new Response.Builder().request(new Request.Builder().url(target).build()).protocol( + Protocol.HTTP_1_1).code(200).message("Some message") + .body(ResponseBody.create( + "someBody", MediaType.parse("application/text"))) + .build(); + + Mockito.doReturn(response).when(httpSvc).get(Mockito.any()); + + /* ACT */ + final var result = service.request(HttpService.Method.GET, target, args); + + /* ASSERT */ + final var expected = service.get(target, args); + assertEquals(expected.getCode(), result.getCode()); + assertTrue(Arrays.areEqual("someBody".getBytes(StandardCharsets.UTF_8), result.getBody().readAllBytes())); + } + + @Test + public void request_null_throwRuntimeException() throws IOException { + /* ARRANGE */ + final var target = new URL("https://someTarget"); + final var args = new HttpService.HttpArgs(); + + /* ACT && ASSERT */ + assertThrows(RuntimeException.class, () -> service.request(null, target, args)); + } + + @Test + public void get_nullTarget_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.get(null, new HttpService.HttpArgs())); + } + + @Test + public void get_nullArgs_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.get(new URL("https://someWhere"), (HttpService.HttpArgs)null)); + } + + @Test + public void get_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.get(null, (HttpService.HttpArgs) null)); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsArtifactBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsArtifactBuilderTest.java new file mode 100644 index 000000000..562009c4d --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsArtifactBuilderTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = { ArtifactFactory.class, IdsArtifactBuilder.class}) +public class IdsArtifactBuilderTest { + + @Autowired + private ArtifactFactory artifactFactory; + + @Autowired + private IdsArtifactBuilder idsArtifactBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsArtifactBuilder.create(null)); + } + + @Test + public void create_defaultDepth_returnCompleteArtifact() { + /* ARRANGE */ + final var artifact = getArtifact(); + + /* ACT */ + final var idsArtifact = idsArtifactBuilder.create(artifact); + + /* ASSERT */ + assertTrue(idsArtifact.getId().isAbsolute()); + assertTrue(idsArtifact.getId().toString().contains(artifact.getId().toString())); + + assertEquals(idsArtifact.getFileName(), artifact.getTitle()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + assertNull(idsArtifact.getProperties()); + } + + @Test + public void create_defaultDepthWithAdditional_returnCompleteArtifact() { + /* ARRANGE */ + final var artifact = getArtifactWithAdditional(); + + /* ACT */ + final var idsArtifact = idsArtifactBuilder.create(artifact); + + /* ASSERT */ + assertTrue(idsArtifact.getId().isAbsolute()); + assertTrue(idsArtifact.getId().toString().contains(artifact.getId().toString())); + + assertEquals(idsArtifact.getFileName(), artifact.getTitle()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + + assertNotNull(idsArtifact.getProperties()); + assertEquals(1, idsArtifact.getProperties().size()); + assertEquals("value", idsArtifact.getProperties().get("key")); + } + + @Test + public void create_maxDepth0_returnCompleteArtifact() { + /* ARRANGE */ + final var artifact = getArtifact(); + + /* ACT */ + final var idsArtifact = idsArtifactBuilder.create(artifact, 0); + + /* ASSERT */ + assertTrue(idsArtifact.getId().isAbsolute()); + assertTrue(idsArtifact.getId().toString().contains(artifact.getId().toString())); + + assertEquals(idsArtifact.getFileName(), artifact.getTitle()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + assertNull(idsArtifact.getProperties()); + } + + @Test + public void create_maxDepth5_returnCompleteArtifact() { + /* ARRANGE */ + final var artifact = getArtifact(); + + /* ACT */ + final var idsArtifact = idsArtifactBuilder.create(artifact, 5); + + /* ASSERT */ + assertTrue(idsArtifact.getId().isAbsolute()); + assertTrue(idsArtifact.getId().toString().contains(artifact.getId().toString())); + + assertEquals(idsArtifact.getFileName(), artifact.getTitle()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + assertNull(idsArtifact.getProperties()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private Artifact getArtifact() { + final var artifactDesc = new ArtifactDesc(); + artifactDesc.setTitle("title"); + artifactDesc.setAutomatedDownload(false); + artifactDesc.setValue("value"); + final var artifact = artifactFactory.create(artifactDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(artifact, date); + + return artifact; + } + + @SneakyThrows + private Artifact getArtifactWithAdditional() { + final var artifact = getArtifact(); + final var additional = new HashMap(); + additional.put("key", "value"); + + final var additionalField = AbstractEntity.class.getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(artifact, additional); + + return artifact; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsCatalogBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsCatalogBuilderTest.java new file mode 100644 index 000000000..7975e6e98 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsCatalogBuilderTest.java @@ -0,0 +1,444 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.CatalogFactory; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractFactory; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.OfferedResourceFactory; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RepresentationFactory; +import io.dataspaceconnector.model.Resource; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.services.ids.builder.IdsCatalogBuilder; +import io.dataspaceconnector.services.ids.builder.IdsContractBuilder; +import io.dataspaceconnector.services.ids.builder.IdsDutyBuilder; +import io.dataspaceconnector.services.ids.builder.IdsPermissionBuilder; +import io.dataspaceconnector.services.ids.builder.IdsProhibitionBuilder; +import io.dataspaceconnector.services.ids.builder.IdsRepresentationBuilder; +import io.dataspaceconnector.services.ids.builder.IdsResourceBuilder; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = { CatalogFactory.class, OfferedResourceFactory.class, + RepresentationFactory.class, ArtifactFactory.class, ContractFactory.class, + ContractRuleFactory.class, IdsCatalogBuilder.class, IdsResourceBuilder.class, + IdsRepresentationBuilder.class, IdsArtifactBuilder.class, IdsContractBuilder.class, + IdsPermissionBuilder.class, IdsProhibitionBuilder.class, IdsDutyBuilder.class, + DeserializationService.class, SerializerProvider.class}) +public class IdsCatalogBuilderTest { + + @Autowired + private CatalogFactory catalogFactory; + + @Autowired + private OfferedResourceFactory resourceFactory; + + @Autowired + private RepresentationFactory representationFactory; + + @Autowired + private ArtifactFactory artifactFactory; + + @Autowired + private ContractFactory contractFactory; + + @Autowired + private ContractRuleFactory ruleFactory; + + @Autowired + private IdsCatalogBuilder idsCatalogBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + private final String title = "title"; + + private final String description = "description"; + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsCatalogBuilder.create(null)); + } + + @Test + public void create_defaultDepth_returnCompleteCatalog() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + assertNull(idsCatalog.getProperties()); + + assertNull(idsCatalog.getRequestedResource()); + + final var offeredResources = idsCatalog.getOfferedResource(); + assertEquals(1, offeredResources.size()); + + final var representations = offeredResources.get(0).getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = offeredResources.get(0).getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + @Test + public void create_defaultDepthWithAdditional_returnCompleteCatalog() { + /* ARRANGE */ + final var catalog = getCatalogWithAdditional(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + + assertNotNull(idsCatalog.getProperties()); + assertEquals(1, idsCatalog.getProperties().size()); + assertEquals("value", idsCatalog.getProperties().get("key")); + + assertNull(idsCatalog.getRequestedResource()); + + final var offeredResources = idsCatalog.getOfferedResource(); + assertEquals(1, offeredResources.size()); + + final var representations = offeredResources.get(0).getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = offeredResources.get(0).getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + @Test + public void create_maxDepth0_returnCatalogWithoutResources() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog, 0); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + assertNull(idsCatalog.getProperties()); + + assertNull(idsCatalog.getRequestedResource()); + assertNull(idsCatalog.getOfferedResource()); + } + + @Test + public void create_maxDepth1_returnCatalogWithoutRepresentationsAndContracts() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog, 1); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + assertNull(idsCatalog.getProperties()); + + assertNull(idsCatalog.getRequestedResource()); + + final var offeredResources = idsCatalog.getOfferedResource(); + assertEquals(0, offeredResources.size()); + } + + @Test + public void create_maxDepth2_returnCatalogWithoutResources() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog, 2); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + assertNull(idsCatalog.getProperties()); + + assertNull(idsCatalog.getRequestedResource()); + + final var offeredResources = idsCatalog.getOfferedResource(); + assertEquals(0, offeredResources.size()); + } + + @Test + public void create_maxDepth5_returnCompleteCatalog() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var idsCatalog = idsCatalogBuilder.create(catalog, 5); + + /* ASSERT */ + assertTrue(idsCatalog.getId().isAbsolute()); + assertTrue(idsCatalog.getId().toString().contains(idsCatalog.getId().toString())); + assertNull(idsCatalog.getProperties()); + + assertNull(idsCatalog.getRequestedResource()); + + final var offeredResources = idsCatalog.getOfferedResource(); + assertEquals(1, offeredResources.size()); + + final var representations = offeredResources.get(0).getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = offeredResources.get(0).getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private Artifact getArtifact() { + final var artifactDesc = new ArtifactDesc(); + artifactDesc.setTitle(title); + artifactDesc.setAutomatedDownload(false); + artifactDesc.setValue("value"); + final var artifact = artifactFactory.create(artifactDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(artifact, date); + + return artifact; + } + + @SneakyThrows + private Representation getRepresentation() { + final var representationDesc = new RepresentationDesc(); + representationDesc.setTitle(title); + representationDesc.setLanguage("EN"); + representationDesc.setMediaType("plain/text"); + representationDesc.setStandard("http://standard.com"); + + final var representation = representationFactory.create(representationDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(representation, ZonedDateTime.now(ZoneOffset.UTC)); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(representation, date); + + final var artifactsField = Representation.class.getDeclaredField("artifacts"); + artifactsField.setAccessible(true); + artifactsField.set(representation, Collections.singletonList(getArtifact())); + + return representation; + } + + @SneakyThrows + private ContractRule getRule() { + final var value = "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle(title); + ruleDesc.setValue(value); + final var rule = ruleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + @SneakyThrows + private Contract getContract() { + final var contractDesc = new ContractDesc(); + contractDesc.setTitle(title); + contractDesc.setStart(date); + contractDesc.setEnd(date); + contractDesc.setProvider(URI.create("http://provider.com")); + contractDesc.setConsumer(URI.create("http://consumer.com")); + + final var contract = contractFactory.create(contractDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(contract, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(contract, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(contract, date); + + final var rulesField = Contract.class.getDeclaredField("rules"); + rulesField.setAccessible(true); + rulesField.set(contract, Collections.singletonList(getRule())); + + return contract; + } + + @SneakyThrows + private OfferedResource getOfferedResource() { + final var resourceDesc = new OfferedResourceDesc(); + resourceDesc.setLanguage("EN"); + resourceDesc.setTitle(title); + resourceDesc.setDescription(description); + resourceDesc.setKeywords(Collections.singletonList("keyword")); + resourceDesc.setEndpointDocumentation(URI.create("http://endpoint-doc.com")); + resourceDesc.setLicence(URI.create("http://license.com")); + resourceDesc.setPublisher(URI.create("http://publisher.com")); + resourceDesc.setSovereign(URI.create("http://sovereign.com")); + + final var resource = resourceFactory.create(resourceDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(resource, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(resource, date); + + final var contractsField = Resource.class.getDeclaredField("contracts"); + contractsField.setAccessible(true); + contractsField.set(resource, Collections.singletonList(getContract())); + + final var representationsField = Resource.class.getDeclaredField("representations"); + representationsField.setAccessible(true); + representationsField.set(resource, Collections.singletonList(getRepresentation())); + + return resource; + } + + @SneakyThrows + private Catalog getCatalog() { + final var catalogDesc = new CatalogDesc(); + catalogDesc.setTitle(title); + catalogDesc.setDescription(description); + final var catalog = catalogFactory.create(catalogDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(catalog, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(catalog, date); + + final var offeredResourcesField = Catalog.class.getDeclaredField("offeredResources"); + offeredResourcesField.setAccessible(true); + offeredResourcesField.set(catalog, Collections.singletonList(getOfferedResource()));; + + return catalog; + } + + @SneakyThrows + private Catalog getCatalogWithAdditional() { + final var catalog = getCatalog(); + final var additional = new HashMap(); + additional.put("key", "value"); + + final var additionalField = AbstractEntity.class.getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(catalog, additional); + + return catalog; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsContractBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsContractBuilderTest.java new file mode 100644 index 000000000..d5ecdbfee --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsContractBuilderTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractFactory; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.services.ids.builder.IdsContractBuilder; +import io.dataspaceconnector.services.ids.builder.IdsDutyBuilder; +import io.dataspaceconnector.services.ids.builder.IdsPermissionBuilder; +import io.dataspaceconnector.services.ids.builder.IdsProhibitionBuilder; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {ContractFactory.class, ContractRuleFactory.class, + IdsContractBuilder.class, IdsPermissionBuilder.class, IdsProhibitionBuilder.class, + IdsDutyBuilder.class, DeserializationService.class, SerializerProvider.class}) +public class IdsContractBuilderTest { + + @Autowired + private ContractFactory contractFactory; + + @Autowired + private ContractRuleFactory ruleFactory; + + @Autowired + private IdsContractBuilder idsContractBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + private final URI provider = URI.create("https://provider.com"); + + private final URI consumer = URI.create("https://consumer.com"); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsContractBuilder.create(null)); + } + + @Test + public void create_defaultDepth_returnCompleteContract() { + /* ARRANGE */ + final var contract = getContract(); + + /* ACT */ + final var idsContract = idsContractBuilder.create(contract); + + /* ASSERT */ + assertTrue(idsContract.getId().isAbsolute()); + assertTrue(idsContract.getId().toString().contains(contract.getId().toString())); + + assertEquals(provider, idsContract.getProvider()); + assertEquals(consumer, idsContract.getConsumer()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractStart()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractEnd()); + assertNull(idsContract.getProperties()); + + assertTrue(idsContract.getProhibition().isEmpty()); + assertTrue(idsContract.getObligation().isEmpty()); + assertEquals(1, idsContract.getPermission().size()); + assertEquals(Action.USE, idsContract.getPermission().get(0).getAction().get(0)); + assertNull(idsContract.getPermission().get(0).getConstraint()); + } + + @Test + public void create_defaultDepthWithAdditional_returnCompleteContract() { + /* ARRANGE */ + final var contract = getContractWithAdditional(); + + /* ACT */ + final var idsContract = idsContractBuilder.create(contract); + + /* ASSERT */ + assertTrue(idsContract.getId().isAbsolute()); + assertTrue(idsContract.getId().toString().contains(contract.getId().toString())); + + assertEquals(provider, idsContract.getProvider()); + assertEquals(consumer, idsContract.getConsumer()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractStart()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractEnd()); + + assertNotNull(idsContract.getProperties()); + assertEquals(1, idsContract.getProperties().size()); + assertEquals("value", idsContract.getProperties().get("key")); + + assertTrue(idsContract.getProhibition().isEmpty()); + assertTrue(idsContract.getObligation().isEmpty()); + assertEquals(1, idsContract.getPermission().size()); + assertEquals(Action.USE, idsContract.getPermission().get(0).getAction().get(0)); + assertNull(idsContract.getPermission().get(0).getConstraint()); + } + + @Test + public void create_maxDepth0_returnNull() { + /* ARRANGE */ + final var contract = getContract(); + + /* ACT */ + final var idsContract = idsContractBuilder.create(contract, 0); + + /* ASSERT */ + assertNull(idsContract); + } + + @Test + public void create_maxDepth5_returnCompleteContract() { + /* ARRANGE */ + final var contract = getContract(); + + /* ACT */ + final var idsContract = idsContractBuilder.create(contract, 5); + + /* ASSERT */ + assertTrue(idsContract.getId().isAbsolute()); + assertTrue(idsContract.getId().toString().contains(contract.getId().toString())); + + assertEquals(provider, idsContract.getProvider()); + assertEquals(consumer, idsContract.getConsumer()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractStart()); + assertEquals(IdsUtils.getGregorianOf(date), idsContract.getContractEnd()); + assertNull(idsContract.getProperties()); + + assertTrue(idsContract.getProhibition().isEmpty()); + assertTrue(idsContract.getObligation().isEmpty()); + assertEquals(1, idsContract.getPermission().size()); + assertEquals(Action.USE, idsContract.getPermission().get(0).getAction().get(0)); + assertNull(idsContract.getPermission().get(0).getConstraint()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private ContractRule getRule() { + final var value = "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle("title"); + ruleDesc.setValue(value); + final var rule = ruleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + @SneakyThrows + private Contract getContract() { + final var contractDesc = new ContractDesc(); + contractDesc.setTitle("title"); + contractDesc.setStart(date); + contractDesc.setEnd(date); + contractDesc.setProvider(provider); + contractDesc.setConsumer(consumer); + + final var contract = contractFactory.create(contractDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(contract, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(contract, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(contract, date); + + final var rulesField = Contract.class.getDeclaredField("rules"); + rulesField.setAccessible(true); + rulesField.set(contract, Collections.singletonList(getRule())); + + return contract; + } + + @SneakyThrows + private Contract getContractWithAdditional() { + final var contract = getContract(); + final var additional = new HashMap(); + additional.put("key", "value"); + + final var additionalField = AbstractEntity.class.getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(contract, additional); + + return contract; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsDutyBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsDutyBuilderTest.java new file mode 100644 index 000000000..c49c79661 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsDutyBuilderTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.BinaryOperator; +import de.fraunhofer.iais.eis.Constraint; +import de.fraunhofer.iais.eis.DutyImpl; +import de.fraunhofer.iais.eis.LeftOperand; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.services.ids.builder.IdsDutyBuilder; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {ContractRuleFactory.class, IdsDutyBuilder.class, + DeserializationService.class, SerializerProvider.class}) +public class IdsDutyBuilderTest { + + @Autowired + private ContractRuleFactory contractRuleFactory; + + @Autowired + private IdsDutyBuilder idsDutyBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsDutyBuilder.create(null)); + } + + @Test + public void create_ruleWithoutId_returnRuleWithNewId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithoutId()); + + /* ACT */ + final var idsRule = idsDutyBuilder.create(rule); + + /* ASSERT */ + assertEquals(DutyImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.DELETE, idsRule.getAction().get(0)); + + assertEquals(1, idsRule.getConstraint().size()); + Constraint constraint = (Constraint) idsRule.getConstraint().get(0); +// assertEquals("xsd:dateTimeStamp", constraint.getRightOperand().getType()); //TODO always null for xsd:dateTimeStamp + assertEquals("2020-07-11T00:00:00Z", constraint.getRightOperand().getValue()); + assertEquals(BinaryOperator.TEMPORAL_EQUALS, constraint.getOperator()); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint.getLeftOperand()); + + assertNull(idsRule.getDescription()); + } + + @Test + public void create_ruleWithId_returnRuleWithReplacedId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithId()); + + /* ACT */ + final var idsRule = idsDutyBuilder.create(rule); + + /* ASSERT */ + assertEquals(DutyImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.DELETE, idsRule.getAction().get(0)); + + assertEquals(1, idsRule.getConstraint().size()); + Constraint constraint = (Constraint) idsRule.getConstraint().get(0); +// assertEquals("xsd:dateTimeStamp", constraint.getRightOperand().getType()); + assertEquals("2020-07-11T00:00:00Z", constraint.getRightOperand().getValue()); + assertEquals(BinaryOperator.TEMPORAL_EQUALS, constraint.getOperator()); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint.getLeftOperand()); + + assertNull(idsRule.getDescription()); + } + + @Test + public void create_invalidRuleJson_throwIllegalArgumentException() { + /* ARRANGE */ + final var json = "{\"not\": \"a rule\"}"; + final var rule = getContractRule(json); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsDutyBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithInvalidType_throwIllegalArgumentException() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithInvalidType()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsDutyBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithMissingAction_returnRuleWithMissingAction() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithMissingAction()); + + /* ACT */ + final var idsRule = idsDutyBuilder.create(rule); + + /* ASSERT */ + assertEquals(DutyImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertNull(idsRule.getAction()); + + assertEquals(1, idsRule.getConstraint().size()); + Constraint constraint = (Constraint) idsRule.getConstraint().get(0); +// assertEquals("xsd:dateTimeStamp", constraint.getRightOperand().getType()); + assertEquals("2020-07-11T00:00:00Z", constraint.getRightOperand().getValue()); + assertEquals(BinaryOperator.TEMPORAL_EQUALS, constraint.getOperator()); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint.getLeftOperand()); + + assertNull(idsRule.getDescription()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private ContractRule getContractRule(final String value) { + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle("title"); + ruleDesc.setValue(value); + final var rule = contractRuleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + private String getRuleWithId() { + return "{\n" + + " \"@type\" : \"ids:Duty\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/duty/770e6abb-dbe5-4ea3-bff5" + + "-aa4c29d29fb5\",\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:DELETE\"\n" + + " } ],\n" + + " \"ids:constraint\" : [ {\n" + + " \"@type\" : \"ids:Constraint\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/constraint/f2acf67f-bc4c-4e64-87fc" + + "-499eec24bc57\",\n" + + " \"ids:rightOperand\" : {\n" + + " \"@value\" : \"2020-07-11T00:00:00Z\",\n" + + " \"@type\" : \"xsd:dateTimeStamp\"\n" + + " },\n" + + " \"ids:operator\" : {\n" + + " \"@id\" : \"idsc:TEMPORAL_EQUALS\"\n" + + " },\n" + + " \"ids:leftOperand\" : {\n" + + " \"@id\" : \"idsc:POLICY_EVALUATION_TIME\"\n" + + " }\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithoutId() { + return "{\n" + + " \"@type\" : \"ids:Duty\",\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:DELETE\"\n" + + " } ],\n" + + " \"ids:constraint\" : [ {\n" + + " \"@type\" : \"ids:Constraint\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/constraint/f2acf67f-bc4c-4e64-87fc" + + "-499eec24bc57\",\n" + + " \"ids:rightOperand\" : {\n" + + " \"@value\" : \"2020-07-11T00:00:00Z\",\n" + + " \"@type\" : \"xsd:dateTimeStamp\"\n" + + " },\n" + + " \"ids:operator\" : {\n" + + " \"@id\" : \"idsc:TEMPORAL_EQUALS\"\n" + + " },\n" + + " \"ids:leftOperand\" : {\n" + + " \"@id\" : \"idsc:POLICY_EVALUATION_TIME\"\n" + + " }\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithInvalidType() { + return "{\n" + + " \"@type\" : \"ids:Representation\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/duty/770e6abb-dbe5-4ea3-bff5" + + "-aa4c29d29fb5\",\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:DELETE\"\n" + + " } ],\n" + + " \"ids:constraint\" : [ {\n" + + " \"@type\" : \"ids:Constraint\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/constraint/f2acf67f-bc4c-4e64-87fc" + + "-499eec24bc57\",\n" + + " \"ids:rightOperand\" : {\n" + + " \"@value\" : \"2020-07-11T00:00:00Z\",\n" + + " \"@type\" : \"xsd:dateTimeStamp\"\n" + + " },\n" + + " \"ids:operator\" : {\n" + + " \"@id\" : \"idsc:TEMPORAL_EQUALS\"\n" + + " },\n" + + " \"ids:leftOperand\" : {\n" + + " \"@id\" : \"idsc:POLICY_EVALUATION_TIME\"\n" + + " }\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithMissingAction() { + return "{\n" + + " \"@type\" : \"ids:Duty\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/duty/770e6abb-dbe5-4ea3-bff5" + + "-aa4c29d29fb5\",\n" + + " \"ids:constraint\" : [ {\n" + + " \"@type\" : \"ids:Constraint\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/constraint/f2acf67f-bc4c-4e64-87fc" + + "-499eec24bc57\",\n" + + " \"ids:rightOperand\" : {\n" + + " \"@value\" : \"2020-07-11T00:00:00Z\",\n" + + " \"@type\" : \"xsd:dateTimeStamp\"\n" + + " },\n" + + " \"ids:operator\" : {\n" + + " \"@id\" : \"idsc:TEMPORAL_EQUALS\"\n" + + " },\n" + + " \"ids:leftOperand\" : {\n" + + " \"@id\" : \"idsc:POLICY_EVALUATION_TIME\"\n" + + " }\n" + + " } ]\n" + + " }"; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsPermissionBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsPermissionBuilderTest.java new file mode 100644 index 000000000..20e455db8 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsPermissionBuilderTest.java @@ -0,0 +1,225 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.PermissionImpl; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.services.ids.builder.IdsPermissionBuilder; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {ContractRuleFactory.class, IdsPermissionBuilder.class, + DeserializationService.class, SerializerProvider.class}) +public class IdsPermissionBuilderTest { + + @Autowired + private ContractRuleFactory contractRuleFactory; + + @Autowired + private IdsPermissionBuilder idsPermissionBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsPermissionBuilder.create(null)); + } + + @Test + public void create_ruleWithoutId_returnRuleWithNewId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithoutId()); + + /* ACT */ + final var idsRule = idsPermissionBuilder.create(rule); + + /* ASSERT */ + assertEquals(PermissionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.USE, idsRule.getAction().get(0)); + assertNull(idsRule.getConstraint()); + assertEquals(1, idsRule.getDescription().size()); + assertEquals("provide-access", idsRule.getDescription().get(0).getValue()); + } + + @Test + public void create_ruleWithId_returnRuleWithReplacedId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithId()); + + /* ACT */ + final var idsRule = idsPermissionBuilder.create(rule); + + /* ASSERT */ + assertEquals(PermissionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.USE, idsRule.getAction().get(0)); + assertNull(idsRule.getConstraint()); + assertEquals(1, idsRule.getDescription().size()); + assertEquals("provide-access", idsRule.getDescription().get(0).getValue()); + } + + @Test + public void create_invalidRuleJson_throwIllegalArgumentException() { + /* ARRANGE */ + final var json = "{\"not\": \"a rule\"}"; + final var rule = getContractRule(json); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsPermissionBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithInvalidType_throwIllegalArgumentException() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithInvalidType()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsPermissionBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithMissingAction_returnRuleWithMissingAction() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithMissingAction()); + + /* ACT */ + final var idsRule = idsPermissionBuilder.create(rule); + + /* ASSERT */ + assertEquals(PermissionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + assertNull(idsRule.getAction()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private ContractRule getContractRule(final String value) { + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle("title"); + ruleDesc.setValue(value); + final var rule = contractRuleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + private String getRuleWithId() { + return "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithoutId() { + return "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithInvalidType() { + return "{\n" + + " \"@type\" : \"ids:Representation\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithMissingAction() { + return "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsProhibitionBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsProhibitionBuilderTest.java new file mode 100644 index 000000000..bbd6dff95 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsProhibitionBuilderTest.java @@ -0,0 +1,225 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.ProhibitionImpl; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.services.ids.builder.IdsProhibitionBuilder; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {ContractRuleFactory.class, IdsProhibitionBuilder.class, + DeserializationService.class, SerializerProvider.class}) +public class IdsProhibitionBuilderTest { + + @Autowired + private ContractRuleFactory contractRuleFactory; + + @Autowired + private IdsProhibitionBuilder idsProhibitionBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsProhibitionBuilder.create(null)); + } + + @Test + public void create_ruleWithoutId_returnRuleWithNewId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithoutId()); + + /* ACT */ + final var idsRule = idsProhibitionBuilder.create(rule); + + /* ASSERT */ + assertEquals(ProhibitionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.USE, idsRule.getAction().get(0)); + assertNull(idsRule.getConstraint()); + assertEquals(1, idsRule.getDescription().size()); + assertEquals("prohibit-access", idsRule.getDescription().get(0).getValue()); + } + + @Test + public void create_ruleWithId_returnRuleWithReplacedId() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithId()); + + /* ACT */ + final var idsRule = idsProhibitionBuilder.create(rule); + + /* ASSERT */ + assertEquals(ProhibitionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + + assertEquals(1, idsRule.getAction().size()); + assertEquals(Action.USE, idsRule.getAction().get(0)); + assertNull(idsRule.getConstraint()); + assertEquals(1, idsRule.getDescription().size()); + assertEquals("prohibit-access", idsRule.getDescription().get(0).getValue()); + } + + @Test + public void create_invalidRuleJson_throwIllegalArgumentException() { + /* ARRANGE */ + final var json = "{\"not\": \"a rule\"}"; + final var rule = getContractRule(json); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsProhibitionBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithInvalidType_throwIllegalArgumentException() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithInvalidType()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> idsProhibitionBuilder.create(rule)); + } + + @Test + public void create_ruleJsonWithMissingAction_returnRuleWithMissingAction() { + /* ARRANGE */ + final var rule = getContractRule(getRuleWithMissingAction()); + + /* ACT */ + final var idsRule = idsProhibitionBuilder.create(rule); + + /* ASSERT */ + assertEquals(ProhibitionImpl.class, idsRule.getClass()); + assertTrue(idsRule.getId().isAbsolute()); + assertTrue(idsRule.getId().toString().contains(rule.getId().toString())); + assertNull(idsRule.getAction()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private ContractRule getContractRule(final String value) { + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle("title"); + ruleDesc.setValue(value); + final var rule = contractRuleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + private String getRuleWithId() { + return "{\n" + + " \"@type\" : \"ids:Prohibition\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/prohibition/ff1b43b9-f3b1-44b1" + + "-a826-2efccc199a76\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"prohibit-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithoutId() { + return "{\n" + + " \"@type\" : \"ids:Prohibition\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"prohibit-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithInvalidType() { + return "{\n" + + " \"@type\" : \"ids:Representation\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/prohibition/ff1b43b9-f3b1-44b1" + + "-a826-2efccc199a76\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"prohibit-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + + private String getRuleWithMissingAction() { + return "{\n" + + " \"@type\" : \"ids:Prohibition\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/prohibition/ff1b43b9-f3b1-44b1" + + "-a826-2efccc199a76\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"prohibit-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsRepresentationBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsRepresentationBuilderTest.java new file mode 100644 index 000000000..f93608d3f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsRepresentationBuilderTest.java @@ -0,0 +1,236 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import de.fraunhofer.iais.eis.Language; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RepresentationFactory; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.services.ids.builder.IdsRepresentationBuilder; +import io.dataspaceconnector.utils.IdsUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {RepresentationFactory.class, ArtifactFactory.class, + IdsRepresentationBuilder.class, IdsArtifactBuilder.class}) +public class IdsRepresentationBuilderTest { + + @Autowired + private RepresentationFactory representationFactory; + + @Autowired + private ArtifactFactory artifactFactory; + + @Autowired + private IdsRepresentationBuilder idsRepresentationBuilder; + + private final String mediaType = "plain/text"; + + private final URI standard = URI.create("http://standard.com"); + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsRepresentationBuilder.create(null)); + } + + @Test + public void create_defaultDepth_returnCompleteRepresentation() { + /* ARRANGE */ + final var representation = getRepresentation(); + + /* ACT */ + final var idsRepresentation = idsRepresentationBuilder.create(representation); + + /* ASSERT */ + assertTrue(idsRepresentation.getId().isAbsolute()); + assertTrue(idsRepresentation.getId().toString().contains(representation.getId().toString())); + + assertEquals(IdsUtils.getGregorianOf(representation.getCreationDate()), idsRepresentation.getCreated()); + assertEquals(Language.EN, idsRepresentation.getLanguage()); + assertEquals(mediaType, idsRepresentation.getMediaType().getFilenameExtension()); + assertEquals(IdsUtils.getGregorianOf(representation.getModificationDate()), idsRepresentation.getModified()); + assertEquals(standard, idsRepresentation.getRepresentationStandard()); + assertNull(idsRepresentation.getProperties()); + + final var artifacts = idsRepresentation.getInstance(); + assertEquals(1, artifacts.size()); + + final var artifact = getArtifact(); + final var idsArtifact = (de.fraunhofer.iais.eis.Artifact) artifacts.get(0); + assertEquals(artifact.getTitle(), idsArtifact.getFileName()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + } + + @Test + public void create_defaultDepthWithAdditional_returnCompleteRepresentation() { + /* ARRANGE */ + final var representation = getRepresentationWithAdditional(); + + /* ACT */ + final var idsRepresentation = idsRepresentationBuilder.create(representation); + + /* ASSERT */ + assertTrue(idsRepresentation.getId().isAbsolute()); + assertTrue(idsRepresentation.getId().toString().contains(representation.getId().toString())); + + assertEquals(IdsUtils.getGregorianOf(representation.getCreationDate()), idsRepresentation.getCreated()); + assertEquals(Language.EN, idsRepresentation.getLanguage()); + assertEquals(mediaType, idsRepresentation.getMediaType().getFilenameExtension()); + assertEquals(IdsUtils.getGregorianOf(representation.getModificationDate()), idsRepresentation.getModified()); + assertEquals(standard, idsRepresentation.getRepresentationStandard()); + + assertNotNull(idsRepresentation.getProperties()); + assertEquals(1, idsRepresentation.getProperties().size()); + assertEquals("value", idsRepresentation.getProperties().get("key")); + + final var artifacts = idsRepresentation.getInstance(); + assertEquals(1, artifacts.size()); + + final var artifact = getArtifact(); + final var idsArtifact = (de.fraunhofer.iais.eis.Artifact) artifacts.get(0); + assertEquals(artifact.getTitle(), idsArtifact.getFileName()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + } + + @Test + public void create_maxDepth0_returnNull() { + /* ARRANGE */ + final var representation = getRepresentation(); + + /* ACT */ + final var idsRepresentation = idsRepresentationBuilder.create(representation, 0); + + /* ASSERT */ + assertNull(idsRepresentation); + } + + @Test + public void create_maxDepth5_returnCompleteRepresentation() { + /* ARRANGE */ + final var representation = getRepresentation(); + + /* ACT */ + final var idsRepresentation = idsRepresentationBuilder.create(representation, 5); + + /* ASSERT */ + assertTrue(idsRepresentation.getId().isAbsolute()); + assertTrue(idsRepresentation.getId().toString().contains(representation.getId().toString())); + + assertEquals(IdsUtils.getGregorianOf(representation.getCreationDate()), idsRepresentation.getCreated()); + assertEquals(Language.EN, idsRepresentation.getLanguage()); + assertEquals(mediaType, idsRepresentation.getMediaType().getFilenameExtension()); + assertEquals(IdsUtils.getGregorianOf(representation.getModificationDate()), idsRepresentation.getModified()); + assertEquals(standard, idsRepresentation.getRepresentationStandard()); + assertNull(idsRepresentation.getProperties()); + + final var artifacts = idsRepresentation.getInstance(); + assertEquals(1, artifacts.size()); + + final var artifact = getArtifact(); + final var idsArtifact = (de.fraunhofer.iais.eis.Artifact) artifacts.get(0); + assertEquals(artifact.getTitle(), idsArtifact.getFileName()); + assertEquals(IdsUtils.getGregorianOf(artifact.getCreationDate()), idsArtifact.getCreationDate()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private Artifact getArtifact() { + final var artifactDesc = new ArtifactDesc(); + artifactDesc.setTitle("title"); + artifactDesc.setAutomatedDownload(false); + artifactDesc.setValue("value"); + final var artifact = artifactFactory.create(artifactDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(artifact, date); + + return artifact; + } + + @SneakyThrows + private Representation getRepresentation() { + final var representationDesc = new RepresentationDesc(); + representationDesc.setTitle("title"); + representationDesc.setLanguage("EN"); + representationDesc.setMediaType(mediaType); + representationDesc.setStandard(standard.toString()); + + final var representation = representationFactory.create(representationDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(representation, ZonedDateTime.now(ZoneOffset.UTC)); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(representation, date); + + final var artifactsField = Representation.class.getDeclaredField("artifacts"); + artifactsField.setAccessible(true); + artifactsField.set(representation, Collections.singletonList(getArtifact())); + + return representation; + } + + @SneakyThrows + private Representation getRepresentationWithAdditional() { + final var representation = getRepresentation(); + final var additional = new HashMap(); + additional.put("key", "value"); + + final var additionalField = AbstractEntity.class.getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(representation, additional); + + return representation; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/IdsResourceBuilderTest.java b/src/test/java/io/dataspaceconnector/services/ids/IdsResourceBuilderTest.java new file mode 100644 index 000000000..75318da59 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/IdsResourceBuilderTest.java @@ -0,0 +1,407 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids; + +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import io.dataspaceconnector.model.AbstractEntity; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractFactory; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.OfferedResourceFactory; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RepresentationFactory; +import io.dataspaceconnector.model.Resource; +import io.dataspaceconnector.services.ids.builder.IdsArtifactBuilder; +import io.dataspaceconnector.services.ids.builder.IdsContractBuilder; +import io.dataspaceconnector.services.ids.builder.IdsDutyBuilder; +import io.dataspaceconnector.services.ids.builder.IdsPermissionBuilder; +import io.dataspaceconnector.services.ids.builder.IdsProhibitionBuilder; +import io.dataspaceconnector.services.ids.builder.IdsRepresentationBuilder; +import io.dataspaceconnector.services.ids.builder.IdsResourceBuilder; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {OfferedResourceFactory.class, RepresentationFactory.class, + ArtifactFactory.class, ContractFactory.class, ContractRuleFactory.class, + IdsResourceBuilder.class, IdsRepresentationBuilder.class, IdsArtifactBuilder.class, + IdsContractBuilder.class, IdsPermissionBuilder.class, IdsProhibitionBuilder.class, + IdsDutyBuilder.class, DeserializationService.class, SerializerProvider.class}) +public class IdsResourceBuilderTest { + + @Autowired + private OfferedResourceFactory resourceFactory; + + @Autowired + private RepresentationFactory representationFactory; + + @Autowired + private ArtifactFactory artifactFactory; + + @Autowired + private ContractFactory contractFactory; + + @Autowired + private ContractRuleFactory ruleFactory; + + @Autowired + private IdsResourceBuilder idsResourceBuilder; + + private final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + private final Language language = Language.EN; + + private final String title = "title"; + + private final String description = "description"; + + private final String keyword = "keyword"; + + private final URI endpointDocumentation = URI.create("http://endpoint-doc.com"); + + private final URI license = URI.create("http://license.com"); + + private final URI publisher = URI.create("http://publisher.com"); + + private final URI sovereign = URI.create("http://sovereign.com"); + + @Test + public void create_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> idsResourceBuilder.create(null)); + } + + @Test + public void create_defaultDepth_returnCompleteResource() { + /* ARRANGE */ + final var resource = getOfferedResource(); + + /* ACT */ + final var idsResource = idsResourceBuilder.create(resource); + + /* ASSERT */ + assertTrue(idsResource.getId().isAbsolute()); + assertTrue(idsResource.getId().toString().contains(idsResource.getId().toString())); + + assertEquals(publisher, idsResource.getPublisher()); + assertEquals(sovereign, idsResource.getSovereign()); + assertEquals(license, idsResource.getStandardLicense()); + assertEquals(1, idsResource.getDescription().size()); + assertEquals(description, idsResource.getDescription().get(0).getValue()); + assertEquals(1, idsResource.getKeyword().size()); + assertEquals(keyword, idsResource.getKeyword().get(0).getValue()); + assertEquals(1, idsResource.getTitle().size()); + assertEquals(title, idsResource.getTitle().get(0).getValue()); + assertNull(idsResource.getProperties()); + + final var representations = idsResource.getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = idsResource.getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + @Test + public void create_defaultDepthWithAdditional_returnCompleteResource() { + /* ARRANGE */ + final var resource = getOfferedResourceWithAdditional(); + + /* ACT */ + final var idsResource = idsResourceBuilder.create(resource); + + /* ASSERT */ + assertTrue(idsResource.getId().isAbsolute()); + assertTrue(idsResource.getId().toString().contains(idsResource.getId().toString())); + + assertEquals(publisher, idsResource.getPublisher()); + assertEquals(sovereign, idsResource.getSovereign()); + assertEquals(license, idsResource.getStandardLicense()); + assertEquals(1, idsResource.getDescription().size()); + assertEquals(description, idsResource.getDescription().get(0).getValue()); + assertEquals(1, idsResource.getKeyword().size()); + assertEquals(keyword, idsResource.getKeyword().get(0).getValue()); + assertEquals(1, idsResource.getTitle().size()); + assertEquals(title, idsResource.getTitle().get(0).getValue()); + + assertNotNull(idsResource.getProperties()); + assertEquals(1, idsResource.getProperties().size()); + assertEquals("value", idsResource.getProperties().get("key")); + + final var representations = idsResource.getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = idsResource.getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + @Test + public void create_maxDepth0_returnNull() { + /* ARRANGE */ + final var resource = getOfferedResource(); + + /* ACT */ + final var idsResource = idsResourceBuilder.create(resource, 0); + + /* ASSERT */ + assertNull(idsResource); + } + + @Test + public void create_maxDepth1_returnNull() { + /* ARRANGE */ + final var resource = getOfferedResource(); + + /* ACT */ + final var idsResource = idsResourceBuilder.create(resource, 1); + + /* ASSERT */ + assertNull(idsResource); + } + + @Test + public void create_maxDepth5_returnCompleteResource() { + /* ARRANGE */ + final var resource = getOfferedResource(); + + /* ACT */ + final var idsResource = idsResourceBuilder.create(resource, 5); + + /* ASSERT */ + assertTrue(idsResource.getId().isAbsolute()); + assertTrue(idsResource.getId().toString().contains(idsResource.getId().toString())); + + assertEquals(publisher, idsResource.getPublisher()); + assertEquals(sovereign, idsResource.getSovereign()); + assertEquals(license, idsResource.getStandardLicense()); + assertEquals(1, idsResource.getDescription().size()); + assertEquals(description, idsResource.getDescription().get(0).getValue()); + assertEquals(1, idsResource.getKeyword().size()); + assertEquals(keyword, idsResource.getKeyword().get(0).getValue()); + assertEquals(1, idsResource.getTitle().size()); + assertEquals(title, idsResource.getTitle().get(0).getValue()); + assertNull(idsResource.getProperties()); + + final var representations = idsResource.getRepresentation(); + assertEquals(1, representations.size()); + assertEquals(1, representations.get(0).getInstance().size()); + + final var contracts = idsResource.getContractOffer(); + assertEquals(1, contracts.size()); + assertEquals(1, contracts.get(0).getPermission().size()); + assertTrue(contracts.get(0).getProhibition().isEmpty()); + assertTrue(contracts.get(0).getObligation().isEmpty()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private Artifact getArtifact() { + final var artifactDesc = new ArtifactDesc(); + artifactDesc.setTitle(title); + artifactDesc.setAutomatedDownload(false); + artifactDesc.setValue("value"); + final var artifact = artifactFactory.create(artifactDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(artifact, date); + + return artifact; + } + + @SneakyThrows + private Representation getRepresentation() { + final var representationDesc = new RepresentationDesc(); + representationDesc.setTitle(title); + representationDesc.setLanguage(language.name()); + representationDesc.setMediaType("plain/text"); + representationDesc.setStandard("http://standard.com"); + + final var representation = representationFactory.create(representationDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(representation, ZonedDateTime.now(ZoneOffset.UTC)); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(representation, date); + + final var artifactsField = Representation.class.getDeclaredField("artifacts"); + artifactsField.setAccessible(true); + artifactsField.set(representation, Collections.singletonList(getArtifact())); + + return representation; + } + + @SneakyThrows + private ContractRule getRule() { + final var value = "{\n" + + " \"@type\" : \"ids:Permission\",\n" + + " \"@id\" : \"https://w3id.org/idsa/autogen/permission/ae138d4f-f01d-4358" + + "-89a7-73e7c560f3de\",\n" + + " \"ids:description\" : [ {\n" + + " \"@value\" : \"provide-access\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ],\n" + + " \"ids:action\" : [ {\n" + + " \"@id\" : \"idsc:USE\"\n" + + " } ],\n" + + " \"ids:title\" : [ {\n" + + " \"@value\" : \"Example Usage Policy\",\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#string\"\n" + + " } ]\n" + + " }"; + + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setTitle(title); + ruleDesc.setValue(value); + final var rule = ruleFactory.create(ruleDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(rule, date); + + return rule; + } + + @SneakyThrows + private Contract getContract() { + final var contractDesc = new ContractDesc(); + contractDesc.setTitle(title); + contractDesc.setStart(date); + contractDesc.setEnd(date); + contractDesc.setProvider(URI.create("http://provider.com")); + contractDesc.setConsumer(URI.create("http://consumer.com")); + + final var contract = contractFactory.create(contractDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(contract, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(contract, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(contract, date); + + final var rulesField = Contract.class.getDeclaredField("rules"); + rulesField.setAccessible(true); + rulesField.set(contract, Collections.singletonList(getRule())); + + return contract; + } + + @SneakyThrows + private OfferedResource getOfferedResource() { + final var resourceDesc = new OfferedResourceDesc(); + resourceDesc.setLanguage(language.name()); + resourceDesc.setTitle(title); + resourceDesc.setDescription(description); + resourceDesc.setKeywords(Collections.singletonList(keyword)); + resourceDesc.setEndpointDocumentation(endpointDocumentation); + resourceDesc.setLicence(license); + resourceDesc.setPublisher(publisher); + resourceDesc.setSovereign(sovereign); + + final var resource = resourceFactory.create(resourceDesc); + + final var idField = AbstractEntity.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.randomUUID()); + + final var creationDateField = AbstractEntity.class.getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(resource, date); + + final var modificationDateField = AbstractEntity.class.getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(resource, date); + + final var contractsField = Resource.class.getDeclaredField("contracts"); + contractsField.setAccessible(true); + contractsField.set(resource, Collections.singletonList(getContract())); + + final var representationsField = Resource.class.getDeclaredField("representations"); + representationsField.setAccessible(true); + representationsField.set(resource, Collections.singletonList(getRepresentation())); + + return resource; + } + + @SneakyThrows + private OfferedResource getOfferedResourceWithAdditional() { + final var resource = getOfferedResource(); + final var additional = new HashMap(); + additional.put("key", "value"); + + final var additionalField = AbstractEntity.class.getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(resource, additional); + + return resource; + } + +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdaterTest.java b/src/test/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdaterTest.java new file mode 100644 index 000000000..8dd150fc3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/updater/ArtifactUpdaterTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.ArtifactBuilder; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.templates.ArtifactTemplate; +import io.dataspaceconnector.services.resources.ArtifactService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ArtifactUpdater.class}) +public class ArtifactUpdaterTest { + + @MockBean + private ArtifactService artifactService; + + @Autowired + private ArtifactUpdater updater; + + private final UUID artifactId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + private final Artifact artifact = getArtifact(); + private final io.dataspaceconnector.model.Artifact dscArtifact = getDscArtifact(); + private final io.dataspaceconnector.model.Artifact dscUpdatedArtifact = getUpdatedDscArtifact(); + private final ArtifactTemplate template = getTemplate(); + + @Test + public void update_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> updater.update(null)); + } + + @Test + public void update_entityUnknownRemoteId_throwsResourceNotFoundException() { + /* ARRANGE */ + Mockito.doReturn(Optional.empty()) + .when(artifactService) + .identifyByRemoteId(Mockito.eq(artifact.getId())); + + /* ACT && ASSERT */ + final var result = assertThrows(ResourceNotFoundException.class, () -> updater.update(artifact)); + assertEquals(artifactId.toString(), result.getMessage()); + } + + @Test + public void update_knownId_returnUpdatedArtifact() { + /* ARRANGE */ + Mockito.doReturn(Optional.of(artifactId)) + .when(artifactService) + .identifyByRemoteId(Mockito.eq(artifact.getId())); + + Mockito.doReturn(dscArtifact) + .when(artifactService) + .get(Mockito.eq(artifactId)); + + Mockito.doReturn(dscUpdatedArtifact) + .when(artifactService) + .update(Mockito.eq(artifactId), Mockito.eq(template.getDesc())); + + /* ACT && ASSERT */ + final var result = updater.update(artifact); + assertEquals(dscUpdatedArtifact, result); + Mockito.verify(artifactService, Mockito.atLeastOnce()).update(Mockito.eq(artifactId), + Mockito.eq(template.getDesc())); + } + + + private Artifact getArtifact() { + return new ArtifactBuilder(URI.create(artifactId.toString())) + ._fileName_("HELLO").build(); + } + + private io.dataspaceconnector.model.Artifact getDscArtifact() { + final var output = new io.dataspaceconnector.model.ArtifactImpl(); + ReflectionTestUtils.setField(output, "title", "SOME TITLE"); + return output; + } + + private io.dataspaceconnector.model.Artifact getUpdatedDscArtifact() { + final var output = new io.dataspaceconnector.model.ArtifactImpl(); + ReflectionTestUtils.setField(output, "title", "HELLO"); + return output; + } + + private ArtifactTemplate getTemplate() { + final var output = new ArtifactTemplate(new ArtifactDesc()); + output.getDesc().setTitle("HELLO"); + output.getDesc().setRemoteId(URI.create("550e8400-e29b-11d4-a716-446655440000")); + output.getDesc().setAutomatedDownload(false); + output.getDesc().setAdditional(new ConcurrentHashMap<>()); + + return output; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdaterTest.java b/src/test/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdaterTest.java new file mode 100644 index 000000000..e35c4fe56 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/updater/RepresentationUpdaterTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import java.net.URI; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.templates.RepresentationTemplate; +import io.dataspaceconnector.services.resources.RepresentationService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {RepresentationUpdater.class}) +public class RepresentationUpdaterTest { + + @MockBean + private RepresentationService representationService; + + @Autowired + private RepresentationUpdater updater; + + private final UUID representationId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + private final Representation representation = getRepresentation(); + private final io.dataspaceconnector.model.Representation dscRepresentation = getDscRepresentation(); + private final io.dataspaceconnector.model.Representation dscUpdatedRepresentation = getUpdatedDscRepresentation(); + private final RepresentationTemplate template = getTemplate(); + + @Test + public void update_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> updater.update(null)); + } + + @Test + public void update_entityUnknownRemoteId_throwsResourceNotFoundException() { + /* ARRANGE */ + Mockito.doReturn(Optional.empty()) + .when(representationService) + .identifyByRemoteId(Mockito.eq(representation.getId())); + + /* ACT && ASSERT */ + final var result = assertThrows(ResourceNotFoundException.class, () -> updater.update(representation)); + assertEquals(representationId.toString(), result.getMessage()); + } + + @Test + public void update_knownId_returnUpdatedRepresentation() { + /* ARRANGE */ + Mockito.doReturn(Optional.of(representationId)) + .when(representationService) + .identifyByRemoteId(Mockito.eq(representation.getId())); + + Mockito.doReturn(dscRepresentation) + .when(representationService) + .get(Mockito.eq(representationId)); + + Mockito.doReturn(dscUpdatedRepresentation) + .when(representationService) + .update(Mockito.eq(representationId), Mockito.eq(template.getDesc())); + + /* ACT && ASSERT */ + final var result = updater.update(representation); + assertEquals(dscUpdatedRepresentation, result); + Mockito.verify(representationService, Mockito.atLeastOnce()).update(Mockito.eq(representationId), + Mockito.eq(template.getDesc())); + } + + private Representation getRepresentation() { + return new RepresentationBuilder(URI.create(representationId.toString())) + ._language_(Language.DE).build(); + } + + private io.dataspaceconnector.model.Representation getDscRepresentation() { + final var output = new io.dataspaceconnector.model.Representation(); + ReflectionTestUtils.setField(output, "language", "SOME Language"); + return output; + } + + private io.dataspaceconnector.model.Representation getUpdatedDscRepresentation() { + final var output = new io.dataspaceconnector.model.Representation(); + ReflectionTestUtils.setField(output, "language", "https://w3id.org/idsa/code/DE"); + return output; + } + + private RepresentationTemplate getTemplate() { + final var output = new RepresentationTemplate(new RepresentationDesc()); + output.getDesc().setLanguage("https://w3id.org/idsa/code/DE"); + output.getDesc().setRemoteId(URI.create("550e8400-e29b-11d4-a716-446655440000")); + output.getDesc().setAdditional(new ConcurrentHashMap<>()); + + return output; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdaterTest.java b/src/test/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdaterTest.java new file mode 100644 index 000000000..2abc348e3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/ids/updater/RequestedResourceUpdaterTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.ids.updater; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceBuilder; +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.templates.ResourceTemplate; +import io.dataspaceconnector.services.resources.RequestedResourceService; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {RequestedResourceUpdater.class}) +public class RequestedResourceUpdaterTest { + @MockBean + private RequestedResourceService requestedResourceService; + + @Autowired + private RequestedResourceUpdater updater; + + private final UUID resourceId = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + private final Resource resource = getResource(); + private final io.dataspaceconnector.model.RequestedResource dscResource = getDscResource(); + private final io.dataspaceconnector.model.RequestedResource dscUpdatedResource = getUpdatedDscResource(); + private final ResourceTemplate template = getTemplate(); + + @Test + public void update_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> updater.update(null)); + } + + @Test + public void update_entityUnknownRemoteId_throwsResourceNotFoundException() { + /* ARRANGE */ + Mockito.doReturn(Optional.empty()) + .when(requestedResourceService) + .identifyByRemoteId(Mockito.eq(resource.getId())); + + /* ACT && ASSERT */ + final var result = assertThrows(ResourceNotFoundException.class, () -> updater.update( + resource)); + assertEquals(resourceId.toString(), result.getMessage()); + } + + @Test + public void update_knownId_returnUpdatedRepresentation() { + /* ARRANGE */ + Mockito.doReturn(Optional.of(resourceId)) + .when(requestedResourceService) + .identifyByRemoteId(Mockito.eq(resource.getId())); + + Mockito.doReturn(dscResource) + .when(requestedResourceService) + .get(Mockito.eq(resourceId)); + + Mockito.doReturn(dscUpdatedResource) + .when(requestedResourceService) + .update(Mockito.eq(resourceId), Mockito.eq(template.getDesc())); + + /* ACT && ASSERT */ + final var result = updater.update(resource); + assertEquals(dscUpdatedResource, result); + Mockito.verify(requestedResourceService, Mockito.atLeastOnce()).update(Mockito.eq( + resourceId), + Mockito.eq(template.getDesc())); + } + + private Resource getResource() { + return new ResourceBuilder(URI.create(resourceId.toString())) + ._language_(new ArrayList<>(List.of(Language.DE))) + .build(); + } + + @SneakyThrows + private io.dataspaceconnector.model.RequestedResource getDscResource() { + final var resourceConstructor = RequestedResource.class.getDeclaredConstructor(); + resourceConstructor.setAccessible(true); + final var output = resourceConstructor.newInstance(); + ReflectionTestUtils.setField(output, "language", "[SOME Language]"); + return output; + } + + @SneakyThrows + private io.dataspaceconnector.model.RequestedResource getUpdatedDscResource() { + final var resourceConstructor = RequestedResource.class.getDeclaredConstructor(); + resourceConstructor.setAccessible(true); + final var output = resourceConstructor.newInstance(); + ReflectionTestUtils.setField(output, "language", "https://w3id.org/idsa/code/DE"); + return output; + } + + private ResourceTemplate getTemplate() { + final var output = new ResourceTemplate<>(new RequestedResourceDesc()); + output.getDesc().setLanguage("[https://w3id.org/idsa/code/DE]"); + output.getDesc().setRemoteId(URI.create("550e8400-e29b-11d4-a716-446655440000")); + output.getDesc().setAdditional(new ConcurrentHashMap<>()); + output.getDesc().setKeywords(new ArrayList<>()); + + return output; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/MessageResponseServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/MessageResponseServiceTest.java new file mode 100644 index 000000000..3ef973446 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/MessageResponseServiceTest.java @@ -0,0 +1,183 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages; + +import java.net.URI; + +import de.fraunhofer.iais.eis.RejectionReason; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import io.dataspaceconnector.services.ids.ConnectorService; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {MessageResponseService.class}) +class MessageResponseServiceTest { + + @MockBean + ConnectorService connectorService; + + @Autowired + MessageResponseService service; + + private final URI connectorId = URI.create("https://someConnectorId"); + private final String outboundVersion = "4.0.0"; + + @BeforeEach + public void init() { + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(outboundVersion); + } + + /** + * handleMessageEmptyException + */ + + @Test + public void handleMessageEmptyException_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.handleMessageEmptyException(null)); + } + + @Test + public void handleMessageEmptyException_validException_BadParametersResponse() { + /* ARRANGE */ + final var exception = new MessageEmptyException("Some problem"); + + /* ACT */ + final var result = service.handleMessageEmptyException(exception); + + /* ASSERT */ + assertTrue(result instanceof ErrorResponse); + + final var error = (ErrorResponse) result; + assertEquals(RejectionReason.BAD_PARAMETERS, error.getRejectionMessage().getRejectionReason()); + assertEquals(exception.getMessage(), error.getErrorMessage()); + assertEquals(connectorId, error.getRejectionMessage().getIssuerConnector()); + assertEquals(outboundVersion, error.getRejectionMessage().getModelVersion()); + } + + /** + * handleInfoModelNotSupportedException + */ + + @Test + public void handleInfoModelNotSupportedException_nullException_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.handleInfoModelNotSupportedException(null, "4.0.0")); + } + + @Test + public void handleInfoModelNotSupportedException_nullVersion_noException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + Assertions + .assertDoesNotThrow(() -> service.handleInfoModelNotSupportedException(new VersionNotSupportedException(""), null)); + } + + @Test + public void handleInfoModelNotSupportedException_validInput_VersionNotSupportedResponse() + throws IllegalAccessException, NoSuchFieldException { + /* ARRANGE */ + final var exception = new VersionNotSupportedException("Some problem"); + final var version = "3.0.0"; + + /* ACT */ + final var result = service.handleInfoModelNotSupportedException(exception, version); + + /* ASSERT */ + assertTrue(result instanceof ErrorResponse); + + final var error = (ErrorResponse) result; + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, error.getRejectionMessage().getRejectionReason()); + assertEquals(exception.getMessage(), error.getErrorMessage()); + assertEquals(connectorId, error.getRejectionMessage().getIssuerConnector()); + assertEquals(outboundVersion, error.getRejectionMessage().getModelVersion()); + } + + /** + * handleResponseMessageBuilderException + */ + + @Test + public void handleResponseMessageBuilderException_nullException_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.handleResponseMessageBuilderException(null, URI.create("https://someUri"), URI.create("https://someUri"))); + } + + + @Test + public void handleResponseMessageBuilderExceptionException_nullIssuer_noException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + Assertions + .assertDoesNotThrow(() -> service.handleResponseMessageBuilderException(new Exception(""), null, URI.create("https://someUri"))); + } + + @Test + public void handleResponseMessageBuilderExceptionException_nullMessageId_noException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + Assertions + .assertDoesNotThrow(() -> service.handleResponseMessageBuilderException(new Exception(""), URI.create("https://someUri"), null)); + } + + @Test + public void handleResponseMessageBuilderExceptionException_validInput_RecipientResponse() { + /* ARRANGE */ + final var exception = new Exception("Some problem"); + final var issuer = URI.create("https://someUri"); + final var messageId = URI.create("https://someUri2"); + + /* ACT */ + final var result = service.handleResponseMessageBuilderException(exception, issuer, messageId); + + /* ASSERT */ + assertTrue(result instanceof ErrorResponse); + + final var error = (ErrorResponse) result; + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, error.getRejectionMessage().getRejectionReason()); + assertEquals("Response could not be constructed.", error.getErrorMessage()); + assertEquals(connectorId, error.getRejectionMessage().getIssuerConnector()); + assertEquals(outboundVersion, error.getRejectionMessage().getModelVersion()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/MessageServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/MessageServiceTest.java new file mode 100644 index 000000000..23216f05b --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/MessageServiceTest.java @@ -0,0 +1,455 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* +package io.dataspaceconnector.services.messages; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ContractAgreementBuilder; +import de.fraunhofer.iais.eis.ContractRequestBuilder; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.messages.ArtifactRequestMessageDesc; +import io.dataspaceconnector.model.messages.ContractAgreementMessageDesc; +import io.dataspaceconnector.model.messages.ContractRequestMessageDesc; +import io.dataspaceconnector.model.messages.DescriptionRequestMessageDesc; +import io.dataspaceconnector.services.EntityResolver; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import io.dataspaceconnector.services.messages.types.ArtifactRequestService; +import io.dataspaceconnector.services.messages.types.ContractAgreementService; +import io.dataspaceconnector.services.messages.types.ContractRequestService; +import io.dataspaceconnector.services.messages.types.DescriptionRequestService; +import io.dataspaceconnector.services.resources.ArtifactService; +import io.dataspaceconnector.services.resources.TemplateBuilder; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import java.net.URI; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +public class MessageServiceTest { + + @Autowired + DescriptionRequestService descService; + + @Autowired + ContractRequestService contractRequestService; + + @Autowired + ContractAgreementService contractAgreementService; + + @Autowired + ArtifactRequestService artifactRequestService; + + @MockBean + DeserializationService deserializationService; + + @MockBean + TemplateBuilder templateBuilder; + + @MockBean + ConnectorService connectorService; + + @MockBean + EntityResolver entityResolver; + + @MockBean + ObjectMapper objectMapper; + + @MockBean + ArtifactService artifactService; + + private final ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1616772571804L), ZoneOffset.UTC); + + @Test + public void sendDescriptionRequestMessage_withoutRequestedElement_returnValidResponse() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var desc = new DescriptionRequestMessageDesc(recipient, null); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(descService.send(Mockito.eq(desc), Mockito.eq(""))).thenReturn(response); + + */ +/* ACT *//* + + final var result = descService.sendMessage(recipient, null); + + */ +/* ARRANGE *//* + + assertEquals(response, result); + } + + @Test + public void sendDescriptionRequestMessage_validRequestedElement_returnValidResponse() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var element = URI.create("https://requestedElement"); + final var desc = new DescriptionRequestMessageDesc(recipient, element); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(descService.send(Mockito.eq(desc), Mockito.eq(""))).thenReturn(response); + + */ +/* ACT *//* + + final var result = descService.sendMessage(recipient, element); + + */ +/* ARRANGE *//* + + assertEquals(response, result); + } + + @Test + public void validateDescriptionResponseMessage_validResponse_returnTrue() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(descService.isValidResponseType(Mockito.eq(response))).thenReturn(true); + + */ +/* ACT *//* + + final var result = descService.validateResponse(response); + + */ +/* ASSERT *//* + + assertTrue(result); + } + + @Test + public void validateDescriptionResponseMessage_invalidResponse_returnFalse() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some invalid header"); + response.put("body", "some invalid body"); + + Mockito.when(descService.isValidResponseType(Mockito.eq(response))).thenReturn(false); + + */ +/* ACT *//* + + final var result = descService.validateResponse(response); + + */ +/* ASSERT *//* + + assertFalse(result); + } + + @Test + public void sendContractRequestMessage_withValidContractRequest_returnValidResponse() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var contractId = URI.create("https://contractRequest"); + final var desc = new ContractRequestMessageDesc(recipient, contractId); + final var request = new ContractRequestBuilder(contractId).build(); + final var requestAsRdf = request.toRdf(); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(contractRequestService.send(Mockito.eq(desc), Mockito.eq(requestAsRdf))).thenReturn(response); + + */ +/* ACT *//* + + final var result = contractRequestService.sendMessage(recipient, request); + + */ +/* ARRANGE *//* + + assertEquals(response, result); + } + + @Test + public void sendContractRequestMessage_withoutContractRequest_throwsIllegalArgumentException() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var contractId = URI.create("https://contractRequest"); + final var desc = new ContractRequestMessageDesc(recipient, contractId); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(contractRequestService.send(Mockito.eq(desc), Mockito.eq(null))).thenReturn(response); + + */ +/* ACT & ARRANGE *//* + + assertThrows(IllegalArgumentException.class, () -> contractRequestService.sendMessage(recipient, null)); + } + + @Test + public void validateContractRequestResponseMessage_validResponse_returnTrue() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(contractRequestService.isValidResponseType(Mockito.eq(response))).thenReturn(true); + + */ +/* ACT *//* + + final var result = contractRequestService.validateResponse(response); + + */ +/* ASSERT *//* + + assertTrue(result); + } + + @Test + public void validateContractRequestResponseMessage_invalidResponse_returnFalse() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(contractRequestService.isValidResponseType(Mockito.eq(response))).thenReturn(false); + + */ +/* ACT *//* + + final var result = contractRequestService.validateResponse(response); + + */ +/* ASSERT *//* + + assertFalse(result); + } + + @Test + public void sendContractAgreementMessage_withValidContractAgreement_returnValidResponse() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var contractId = URI.create("https://contractAgreement"); + final var desc = new ContractAgreementMessageDesc(recipient, contractId); + final var agreement = new ContractAgreementBuilder(contractId) + ._contractStart_(getDateAsXMLGregorianCalendar()) + .build(); + final var agreementAsRdf = agreement.toRdf(); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(contractAgreementService.send(Mockito.eq(desc), Mockito.eq(agreementAsRdf))).thenReturn(response); + + */ +/* ACT *//* + + final var result = contractAgreementService.sendMessage(recipient, agreement); + + */ +/* ASSERT *//* + + assertEquals(response, result); + } + + @Test + public void sendContractAgreementMessage_withoutContractAgreement_throwsIllegalArgumentException() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var contractId = URI.create("https://contractAgreement"); + final var desc = new ContractAgreementMessageDesc(recipient, contractId); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(contractAgreementService.send(Mockito.eq(desc), Mockito.eq(null))).thenReturn(response); + + */ +/* ACT & ARRANGE *//* + + assertThrows(IllegalArgumentException.class, () -> contractAgreementService.sendMessage(recipient, null)); + } + + @Test + public void validateContractAgreementResponseMessage_validResponse_returnTrue() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(contractAgreementService.isValidResponseType(Mockito.eq(response))).thenReturn(true); + + */ +/* ACT *//* + + final var result = contractAgreementService.validateResponse(response); + + */ +/* ASSERT *//* + + assertTrue(result); + } + + @Test + public void validateContractAgreementResponseMessage_invalidResponse_returnFalse() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(contractAgreementService.isValidResponseType(Mockito.eq(response))).thenReturn(false); + + */ +/* ACT *//* + + final var result = contractAgreementService.validateResponse(response); + + */ +/* ASSERT *//* + + assertFalse(result); + } + + @Test + public void sendArtifactRequestMessage_withValidInput_returnValidResponse() { + */ +/* ARRANGE *//* + + final var recipient = URI.create("https://localhost:8080/api/ids/data"); + final var elementId = URI.create("https://element"); + final var agreementId = URI.create("https://agreement"); + final var desc = new ArtifactRequestMessageDesc(recipient, elementId, agreementId); + + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("body", "some body values"); + + Mockito.when(artifactRequestService.send(Mockito.eq(desc), Mockito.eq(""))).thenReturn(response); + + */ +/* ACT *//* + + final var result = artifactRequestService.sendMessage(recipient, elementId, agreementId); + + */ +/* ARRANGE *//* + + assertEquals(response, result); + } + + @Test + public void validateArtifactResponseMessage_validResponse_returnTrue() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(artifactRequestService.isValidResponseType(Mockito.eq(response))).thenReturn(true); + + */ +/* ACT *//* + + final var result = artifactRequestService.validateResponse(response); + + */ +/* ASSERT *//* + + assertTrue(result); + } + + @Test + public void validateArtifactResponseMessage_invalidResponse_returnFalse() { + */ +/* ARRANGE *//* + + final var response = new HashMap(); + response.put("header", "some valid header"); + response.put("body", "some valid body"); + + Mockito.when(artifactRequestService.isValidResponseType(Mockito.eq(response))).thenReturn(false); + + */ +/* ACT *//* + + final var result = artifactRequestService.validateResponse(response); + + */ +/* ASSERT *//* + + assertFalse(result); + } + + @SneakyThrows + private XMLGregorianCalendar getDateAsXMLGregorianCalendar() { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(Date.from(date.toInstant())); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + } +} +*/ diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandlerTest.java new file mode 100644 index 000000000..49e169c8a --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/ArtifactRequestHandlerTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.io.InputStream; +import java.net.URI; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ArtifactRequestMessageBuilder; +import de.fraunhofer.iais.eis.ArtifactRequestMessageImpl; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.TokenFormat; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayloadImpl; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class ArtifactRequestHandlerTest { + + @Autowired + ArtifactRequestHandler handler; + + @Test + public void handleMessage_nullMessage_returnBadParametersResponse() { + /* ARRANGE */ + final var payload = new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper()); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage(null, payload); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_unsupportedMessage_returnUnsupportedVersionRejectionMessage() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ArtifactRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("tetris") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + ._requestedArtifact_(URI.create("https://someArtifact")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ArtifactRequestMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_nullPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ArtifactRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + ._requestedArtifact_(URI.create("https://someArtifact")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ArtifactRequestMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_emptyPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ArtifactRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + ._requestedArtifact_(URI.create("https://someArtifact")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ArtifactRequestMessageImpl) message, new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandlerTest.java new file mode 100644 index 000000000..2294d28e0 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/ContractAgreementHandlerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.io.InputStream; +import java.net.URI; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ContractAgreementMessageBuilder; +import de.fraunhofer.iais.eis.ContractAgreementMessageImpl; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.TokenFormat; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayloadImpl; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class ContractAgreementHandlerTest { + + @Autowired + ContractAgreementHandler handler; + + @Test + public void handleMessage_nullMessage_returnBadParametersResponse() { + /* ARRANGE */ + final var payload = new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper()); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage(null, payload); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_unsupportedMessage_returnUnsupportedVersionRejectionMessage() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractAgreementMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("tetris") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractAgreementMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, result.getRejectionMessage().getRejectionReason()); + } + + + @Test + public void handleMessage_nullPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractAgreementMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractAgreementMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_emptyPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractAgreementMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + ._correlationMessage_(URI.create("https://somecorrelationMessage")) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractAgreementMessageImpl) message, new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandlerTest.java new file mode 100644 index 000000000..3cfedf7c8 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/ContractRequestHandlerTest.java @@ -0,0 +1,451 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractAgreementBuilder; +import de.fraunhofer.iais.eis.ContractAgreementMessage; +import de.fraunhofer.iais.eis.ContractRequestBuilder; +import de.fraunhofer.iais.eis.ContractRequestMessageBuilder; +import de.fraunhofer.iais.eis.ContractRequestMessageImpl; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.PermissionBuilder; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.TokenFormat; +import de.fraunhofer.iais.eis.ids.jsonld.Serializer; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractFactory; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import io.dataspaceconnector.services.EntityPersistenceService; +import io.dataspaceconnector.services.resources.EntityDependencyResolver; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayloadImpl; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import de.fraunhofer.isst.ids.framework.util.IDSUtils; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +class ContractRequestHandlerTest { + + @SpyBean + EntityPersistenceService persistenceService; + + @SpyBean + EntityDependencyResolver dependencyResolver; + + @Autowired + ContractRequestHandler handler; + + @Test + public void handleMessage_nullMessage_returnBadParametersResponse() { + /* ARRANGE */ + final var payload = new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper()); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage(null, payload); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_unsupportedMessage_returnUnsupportedVersionRejectionMessage() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("tetris") + ._issued_(xmlCalendar) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractRequestMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_nullPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractRequestMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_emptyPayload_returnBadRequestErrorResponse() throws + DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new ContractRequestMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + .build(); + + /* ACT */ + final var result = (ErrorResponse)handler.handleMessage((ContractRequestMessageImpl) message, new MessagePayloadImpl(InputStream.nullInputStream(), new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_nullPayload_returnInternalRecipientErrorResponse() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = (ErrorResponse)handler.processContractRequest(null, URI.create("https://someUri"), URI.create("https://someUri")); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_emptyPayload_returnInternalRecipientErrorResponse() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = (ErrorResponse)handler.processContractRequest("", URI.create("https://someUri"), URI.create("https://someUri")); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_invalidPayload_returnInternalRecipientErrorResponse() { + /* ARRANGE */ + final var payload = "something that is not a contract request."; + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://someUri"); + + /* ACT */ + final var result = (ErrorResponse)handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_contractEmptyRules_returnBadParametersErrorResponse() + throws IOException { + /* ARRANGE */ + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://someUri"); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_noTargetInRule_returnBadParametersErrorResponse() + throws IOException { + /* ARRANGE */ + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + .build())) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://someUri"); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_notAValidTarget_returnInternalRecipientErrorResponse() + throws IOException { + /* ARRANGE */ + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(URI.create("https://someUri/")) + .build())) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://someUri"); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_unknownTarget_returnNotFoundErrorResponse() + throws IOException { + /* ARRANGE */ + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000")) + .build())) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.NOT_FOUND, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_noContractOffers_returnNotFoundErrorResponse() + throws IOException { + /* ARRANGE */ + final var artifactId = URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000"); + + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(artifactId) + .build())) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + Mockito.doReturn(new ArrayList()).when(dependencyResolver).getContractOffersByArtifactId(Mockito.eq(artifactId)); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.NOT_FOUND, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_onlyRestrictedContracts_returnNotFoundErrorResponse() throws IOException { + /* ARRANGE */ + final var artifactId = URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000"); + + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(artifactId) + .build())) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + final var desc = new ContractDesc(); + desc.setConsumer(URI.create("https://someConsumer")); + final var contract = new ContractFactory().create(desc); + + Mockito.doReturn(Arrays.asList(contract)).when(dependencyResolver).getContractOffersByArtifactId(Mockito.eq(artifactId)); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.NOT_FOUND, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_invalidRulesInContract_rejectContractWithMalformedMessageResponse() throws IOException { + /* ARRANGE */ + final var artifactId = URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000"); + + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(artifactId) + .build())) + .build(); + + final var payload = new Serializer().serialize(message).replace("idsc:USE", "idsc:DONTNOW"); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + final var desc = new ContractDesc(); + desc.setConsumer(issuerConnector); + final var contract = new ContractFactory().create(desc); + + Mockito.doReturn(Arrays.asList(contract)).when(dependencyResolver).getContractOffersByArtifactId(Mockito.eq(artifactId)); + Mockito.doThrow(IllegalArgumentException.class).when(dependencyResolver).getRulesByContractOffer(Mockito.eq(contract)); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.MALFORMED_MESSAGE, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_validRequestCannotStore_returnInvalidRecipientError() throws IOException { + /* ARRANGE */ + final var artifactId = URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000"); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(artifactId) + .build(); + + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(permission)) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + final var contractDesc = new ContractDesc(); + contractDesc.setConsumer(issuerConnector); + final var contract = new ContractFactory().create(contractDesc); + + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setValue(new Serializer().serialize(permission)); + final var rule = new ContractRuleFactory().create(ruleDesc); + + Mockito.doReturn(Arrays.asList(contract)).when(dependencyResolver).getContractOffersByArtifactId(Mockito.eq(artifactId)); + Mockito.doReturn(Arrays.asList(rule)).when(dependencyResolver).getRulesByContractOffer(Mockito.eq(contract)); + + /* ACT */ + final var result = (ErrorResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void checkContractRequest_validRequest_returnOk() throws IOException { + /* ARRANGE */ + final var artifactId = URI.create("https://localhost:8080/api/artifacts/550e8400-e29b-11d4-a716-446655440000"); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._target_(artifactId) + .build(); + + final var message = + new ContractRequestBuilder(URI.create("https://someUri")) + ._permission_(Util.asList(permission)) + .build(); + + final var payload = new Serializer().serialize(message); + final var messageId = URI.create("https://someUri"); + final var issuerConnector = URI.create("https://localhost:8080"); + + final var contractDesc = new ContractDesc(); + contractDesc.setConsumer(issuerConnector); + final var contract = new ContractFactory().create(contractDesc); + + final var ruleDesc = new ContractRuleDesc(); + ruleDesc.setValue(new Serializer().serialize(permission)); + final var rule = new ContractRuleFactory().create(ruleDesc); + + Mockito.doReturn(Arrays.asList(contract)).when(dependencyResolver).getContractOffersByArtifactId(Mockito.eq(artifactId)); + Mockito.doReturn(Arrays.asList(rule)).when(dependencyResolver).getRulesByContractOffer(Mockito.eq(contract)); + Mockito.doReturn(getContractAgreement()).when(persistenceService).buildAndSaveContractAgreement( + Mockito.any(), Mockito.eq(Arrays.asList(artifactId)), Mockito.eq(issuerConnector)); + + /* ACT */ + final var result = (BodyResponse) handler.processContractRequest(payload, messageId, issuerConnector); + + /* ASSERT */ + assertTrue(result.getHeader() instanceof ContractAgreementMessage); + } + + private ContractAgreement getContractAgreement() { + return new ContractAgreementBuilder(URI.create("http://localhost:8080/api/agreements/" + UUID.randomUUID())) + ._contractStart_(IDSUtils.getGregorianNow()) + ._contractEnd_(IDSUtils.getGregorianNow()) + .build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandlerTest.java new file mode 100644 index 000000000..639ba37c3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/DescriptionRequestHandlerTest.java @@ -0,0 +1,267 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// package io.dataspaceconnector.services.messages.handler; + +// import static org.junit.jupiter.api.Assertions.assertEquals; + +// import org.junit.jupiter.api.Test; +// import org.mockito.Mockito; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.context.SpringBootTest; +// import org.springframework.boot.test.mock.mockito.MockBean; +// import org.springframework.boot.test.mock.mockito.SpyBean; + +// import java.net.URI; +// import java.util.ArrayList; +// import java.util.Date; +// import java.util.GregorianCalendar; + +// import javax.xml.datatype.DatatypeConfigurationException; +// import javax.xml.datatype.DatatypeFactory; + +// import de.fraunhofer.iais.eis.BaseConnectorBuilder; +// import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +// import de.fraunhofer.iais.eis.DescriptionRequestMessageBuilder; +// import de.fraunhofer.iais.eis.DescriptionRequestMessageImpl; +// import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +// import de.fraunhofer.iais.eis.RejectionReason; +// import de.fraunhofer.iais.eis.SecurityProfile; +// import de.fraunhofer.iais.eis.TokenFormat; +// import de.fraunhofer.iais.eis.util.Util; +// import io.dataspaceconnector.exceptions.ResourceNotFoundException; +// import io.dataspaceconnector.model.ArtifactDesc; +// import io.dataspaceconnector.model.ArtifactFactory; +// import io.dataspaceconnector.services.EntityResolver; +// import io.dataspaceconnector.services.ids.ConnectorService; +// import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +// import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; + +// @SpringBootTest +// class DescriptionRequestHandlerTest { +// @SpyBean +// ConnectorService connectorService; + +// @MockBean +// EntityResolver resolver; + +// @Autowired +// DescriptionRequestHandler handler; + +// @Test +// public void handleMessage_validSelfDescriptionMsg_returnSelfDescription() +// throws DatatypeConfigurationException { +// /* ARRANGE */ +// final var connector = new BaseConnectorBuilder() +// ._resourceCatalog_(new ArrayList<>()) +// ._outboundModelVersion_("4.0.0") +// ._inboundModelVersion_(Util.asList("4.0.0")) +// ._maintainer_(URI.create("https://someMaintainer")) +// ._curator_(URI.create("https://someCurator")) +// ._hasDefaultEndpoint_(new ConnectorEndpointBuilder( +// URI.create("https://someEndpoint")) +// ._accessURL_(URI.create("https://someAccessUrl")) +// .build()) +// ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) +// .build(); + +// final var calendar = new GregorianCalendar(); +// calendar.setTime(new Date()); +// final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + +// final var message = new DescriptionRequestMessageBuilder() +// ._senderAgent_(URI.create("https://localhost:8080")) +// ._issuerConnector_(URI.create("https://localhost:8080")) +// ._securityToken_(new DynamicAttributeTokenBuilder() +// ._tokenFormat_(TokenFormat.OTHER) +// ._tokenValue_("") +// .build()) +// ._modelVersion_("4.0.0") +// ._issued_(xmlCalendar) +// .build(); + +// Mockito.doReturn(connector).when(connectorService).getConnectorWithOfferedResources(); + +// /* ACT */ +// final var result = +// (BodyResponse) handler.handleMessage((DescriptionRequestMessageImpl) message, null); + +// /* ASSERT */ +// final var expected = (BodyResponse) handler.constructSelfDescription( +// message.getIssuerConnector(), message.getId()); + +// // Compare payload +// assertEquals(expected.getPayload(), result.getPayload()); + +// // Compare header +// assertEquals( +// expected.getHeader().getIssuerConnector(), result.getHeader().getIssuerConnector()); +// assertEquals(expected.getHeader().getAuthorizationToken(), +// result.getHeader().getAuthorizationToken()); +// assertEquals(expected.getHeader().getComment().toString(), +// result.getHeader().getComment().toString()); +// assertEquals(expected.getHeader().getCorrelationMessage(), +// result.getHeader().getCorrelationMessage()); +// assertEquals( +// expected.getHeader().getContentVersion(), result.getHeader().getContentVersion()); +// assertEquals(expected.getHeader().getLabel().toString(), +// result.getHeader().getLabel().toString()); +// assertEquals(expected.getHeader().getModelVersion(), result.getHeader().getModelVersion()); +// assertEquals(expected.getHeader().getProperties(), result.getHeader().getProperties()); +// assertEquals( +// expected.getHeader().getRecipientAgent(), result.getHeader().getRecipientAgent()); +// assertEquals(expected.getHeader().getRecipientConnector(), +// result.getHeader().getRecipientConnector()); +// assertEquals(expected.getHeader().getSenderAgent(), result.getHeader().getSenderAgent()); +// assertEquals(expected.getHeader().getTransferContract(), +// result.getHeader().getTransferContract()); +// } + +// @Test +// public void handleMessage_validResourceDescriptionMsgKnownId_returnResourceDescription() +// throws DatatypeConfigurationException { +// /* ARRANGE */ +// final var artifact = new ArtifactFactory().create(new ArtifactDesc()); + +// final var calendar = new GregorianCalendar(); +// calendar.setTime(new Date()); +// final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + +// final var message = +// new DescriptionRequestMessageBuilder() +// ._senderAgent_(URI.create("https://localhost:8080")) +// ._issuerConnector_(URI.create("https://localhost:8080")) +// ._securityToken_(new DynamicAttributeTokenBuilder() +// ._tokenFormat_(TokenFormat.OTHER) +// ._tokenValue_("") +// .build()) +// ._modelVersion_("4.0.0") +// ._requestedElement_(URI.create("https://localhost/8080/api/artifacts/")) +// ._issued_(xmlCalendar) +// .build(); + +// Mockito.when(resolver.getEntityById(Mockito.eq(message.getRequestedElement()))) +// .thenReturn(artifact); + +// /* ACT */ +// final var result = +// (BodyResponse) handler.handleMessage((DescriptionRequestMessageImpl) message, null); + +// /* ASSERT */ +// final var expected = (BodyResponse) handler.constructResourceDescription( +// message.getRequestedElement(), message.getIssuerConnector(), message.getId()); + +// // Compare payload +// assertEquals(expected.getPayload(), result.getPayload()); + +// // Compare header +// assertEquals( +// expected.getHeader().getIssuerConnector(), result.getHeader().getIssuerConnector()); +// assertEquals(expected.getHeader().getAuthorizationToken(), +// result.getHeader().getAuthorizationToken()); +// assertEquals(expected.getHeader().getComment().toString(), +// result.getHeader().getComment().toString()); +// assertEquals(expected.getHeader().getCorrelationMessage(), +// result.getHeader().getCorrelationMessage()); +// assertEquals( +// expected.getHeader().getContentVersion(), result.getHeader().getContentVersion()); +// assertEquals(expected.getHeader().getLabel().toString(), +// result.getHeader().getLabel().toString()); +// assertEquals(expected.getHeader().getModelVersion(), result.getHeader().getModelVersion()); +// assertEquals(expected.getHeader().getProperties(), result.getHeader().getProperties()); +// assertEquals( +// expected.getHeader().getRecipientAgent(), result.getHeader().getRecipientAgent()); +// assertEquals(expected.getHeader().getRecipientConnector(), +// result.getHeader().getRecipientConnector()); +// assertEquals(expected.getHeader().getSenderAgent(), result.getHeader().getSenderAgent()); +// assertEquals(expected.getHeader().getTransferContract(), +// result.getHeader().getTransferContract()); +// } + +// @Test +// public void handleMessage_nullMessage_returnBadParametersRejectionMessage() { +// /* ARRANGE */ +// // Nothing to arrange here. + +// /* ACT */ +// final var result = (ErrorResponse) handler.handleMessage(null, null); + +// /* ASSERT */ +// assertEquals( +// RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); +// } + +// @Test +// public void handleMessage_unsupportedMessage_returnUnsupportedVersionRejectionMessage() +// throws DatatypeConfigurationException { +// /* ARRANGE */ +// final var calendar = new GregorianCalendar(); +// calendar.setTime(new Date()); +// final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + +// final var message = +// new DescriptionRequestMessageBuilder() +// ._senderAgent_(URI.create("https://localhost:8080")) +// ._issuerConnector_(URI.create("https://localhost:8080")) +// ._securityToken_(new DynamicAttributeTokenBuilder() +// ._tokenFormat_(TokenFormat.OTHER) +// ._tokenValue_("") +// .build()) +// ._modelVersion_("tetris") +// ._requestedElement_(URI.create("https://localhost/8080/api/artifacts/")) +// ._issued_(xmlCalendar) +// .build(); + +// /* ACT */ +// final var result = (ErrorResponse) handler.handleMessage( +// (DescriptionRequestMessageImpl) message, null); + +// /* ASSERT */ +// assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, +// result.getRejectionMessage().getRejectionReason()); +// } + +// @Test +// public void handleMessage_validResourceDescriptionMsgUnknownId_returnNotFoundRejectionReason() +// throws DatatypeConfigurationException { +// /* ARRANGE */ +// final var calendar = new GregorianCalendar(); +// calendar.setTime(new Date()); +// final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + +// final var message = +// new DescriptionRequestMessageBuilder() +// ._senderAgent_(URI.create("https://localhost:8080")) +// ._issuerConnector_(URI.create("https://localhost:8080")) +// ._securityToken_(new DynamicAttributeTokenBuilder() +// ._tokenFormat_(TokenFormat.OTHER) +// ._tokenValue_("") +// .build()) +// ._modelVersion_("4.0.0") +// ._requestedElement_(URI.create("https://localhost/8080/api/artifacts/")) +// ._issued_(xmlCalendar) +// .build(); + +// Mockito.when(resolver.getEntityById(Mockito.eq(message.getRequestedElement()))) +// .thenThrow(ResourceNotFoundException.class); + +// /* ACT */ +// final var result = (ErrorResponse) handler.handleMessage( +// (DescriptionRequestMessageImpl) message, null); + +// /* ASSERT */ +// assertEquals(RejectionReason.NOT_FOUND, result.getRejectionMessage().getRejectionReason()); +// } +// } diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandlerTest.java new file mode 100644 index 000000000..0368274c3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/NotificationMessageHandlerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.net.URI; +import java.util.Date; +import java.util.GregorianCalendar; + +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessage; +import de.fraunhofer.iais.eis.NotificationMessageBuilder; +import de.fraunhofer.iais.eis.NotificationMessageImpl; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.services.messages.types.MessageProcessedNotificationService; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class NotificationMessageHandlerTest { + + @SpyBean + MessageProcessedNotificationService notificationService; + + @Autowired + NotificationMessageHandler handler; + + @Test + public void handleMessage_nullMessage_returnBadRequest() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage(null, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_nullMessage_returnVersionNotSupported() throws DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new NotificationMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("tetris") + ._issued_(xmlCalendar) + .build(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((NotificationMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_validMsg_returnMessageProcessNotification() throws DatatypeConfigurationException { + /* ARRANGE */ + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + final var message = new NotificationMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + .build(); + + /* ACT */ + final var result = (BodyResponse) handler.handleMessage((NotificationMessageImpl) message, null); + + /* ASSERT */ + final var expected = (MessageProcessedNotificationMessage) notificationService.buildMessage(new MessageProcessedNotificationMessageDesc( + message.getIssuerConnector(), message.getId())); + + // Compare header + assertEquals(expected.getIssuerConnector(), result.getHeader().getIssuerConnector()); + assertEquals(expected.getAuthorizationToken(), result.getHeader().getAuthorizationToken()); + assertEquals(expected.getComment().toString(), result.getHeader().getComment().toString()); + assertEquals(expected.getCorrelationMessage(), result.getHeader().getCorrelationMessage()); + assertEquals(expected.getContentVersion(), result.getHeader().getContentVersion()); + assertEquals(expected.getLabel().toString(), result.getHeader().getLabel().toString()); + assertEquals(expected.getModelVersion(), result.getHeader().getModelVersion()); + assertEquals(expected.getProperties(), result.getHeader().getProperties()); + assertEquals(expected.getRecipientAgent(), result.getHeader().getRecipientAgent()); + assertEquals(expected.getRecipientConnector(), result.getHeader().getRecipientConnector()); + assertEquals(expected.getSenderAgent(), result.getHeader().getSenderAgent()); + assertEquals(expected.getTransferContract(), result.getHeader().getTransferContract()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandlerTest.java b/src/test/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandlerTest.java new file mode 100644 index 000000000..facb6a7dd --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/handler/ResourceUpdateMessageHandlerTest.java @@ -0,0 +1,247 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iais.eis.ArtifactBuilder; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessage; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.iais.eis.ResourceUpdateMessage; +import de.fraunhofer.iais.eis.ResourceUpdateMessageBuilder; +import de.fraunhofer.iais.eis.ResourceUpdateMessageImpl; +import de.fraunhofer.iais.eis.TokenFormat; +import de.fraunhofer.iais.eis.ids.jsonld.Serializer; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.services.EntityUpdateService; +import de.fraunhofer.isst.ids.framework.messaging.model.messages.MessagePayloadImpl; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.BodyResponse; +import de.fraunhofer.isst.ids.framework.messaging.model.responses.ErrorResponse; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import javax.xml.datatype.DatatypeFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.GregorianCalendar; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +class ResourceUpdateMessageHandlerTest { + + @SpyBean + EntityUpdateService updateService; + + @Autowired + ResourceUpdateMessageHandler handler; + + @Test + public void handleMessage_nullMessage_returnBadRequest() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage(null, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_nullMessage_returnVersionNotSupported() { + /* ARRANGE */ + final var message = getResourceUpdateMessageWithInvalidVersion(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.VERSION_NOT_SUPPORTED, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_missingAffectedResource_returnBadRequestResponseMessage() { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_nullPayload_returnBadRequestResponseMessage() { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, null); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_illPayload_returnBadRequestResponseMessage() { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, new MessagePayloadImpl(null, new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_emptyPayload_returnBadRequestResponseMessage() { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, new MessagePayloadImpl( + InputStream.nullInputStream(), new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_notIdsInPayload_returnInternalRecipientErrorResponseError() { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + final var invalidInput = "some stuff inside here"; + final InputStream stream = new ByteArrayInputStream(invalidInput.getBytes(StandardCharsets.UTF_8)); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, + new MessagePayloadImpl(stream, new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.INTERNAL_RECIPIENT_ERROR, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_affectedResourceNotInPayload_returnBadRequestErrorResponseError() + throws IOException { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + final var validInput = new Serializer().serialize(new ResourceBuilder(URI.create("https://localhost:8080/artifacts/someOtherId")) + .build()); + final InputStream stream = new ByteArrayInputStream(validInput.getBytes(StandardCharsets.UTF_8)); + + /* ACT */ + final var result = (ErrorResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, + new MessagePayloadImpl(stream, new ObjectMapper())); + + /* ASSERT */ + assertEquals(RejectionReason.BAD_PARAMETERS, result.getRejectionMessage().getRejectionReason()); + } + + @Test + public void handleMessage_failToUpdateResource_returnMessageProcessNotification() throws IOException { + /* ARRANGE */ + + final var message = getResourceUpdateMessage(); + final var validInput = new Serializer().serialize(new ResourceBuilder(URI.create("https://localhost:8080/resources/someId")) + .build()); + final InputStream stream = new ByteArrayInputStream(validInput.getBytes(StandardCharsets.UTF_8)); + + // Mockito.doThrow(ResourceNotFoundException.class).when(updateService).updateResource(Mockito.any()); + + /* ACT */ + final var result = (BodyResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, + new MessagePayloadImpl(stream, new ObjectMapper())); + + /* ASSERT */ + assertTrue(result.getHeader() instanceof MessageProcessedNotificationMessage); + } + + + @Test + public void handleMessage_validUpdate_returnMessageProcessNotification() throws IOException { + /* ARRANGE */ + final var message = getResourceUpdateMessage(); + + final var artifact =new ArtifactBuilder(URI.create("https://localhost:8080/artifacts/someId")).build(); + final var representation = new RepresentationBuilder(URI.create("https://localhost:8080/representations/someId")) + ._instance_(Util.asList(artifact)) + .build(); + final var resource = new ResourceBuilder(URI.create("https://localhost:8080/resources/someId")) + ._representation_(Util.asList(representation)).build(); + + final var validInput = new Serializer().serialize(resource); + final InputStream stream = new ByteArrayInputStream(validInput.getBytes(StandardCharsets.UTF_8)); + + + /* ACT */ + final var result = (BodyResponse) handler.handleMessage((ResourceUpdateMessageImpl) message, + new MessagePayloadImpl(stream, new ObjectMapper())); + + /* ASSERT TODO*/ + // Mockito.verify(updateService).updateResource(Mockito.argThat(x -> x.getId().equals(resource.getId()))); + // Mockito.verify(updateService).updateRepresentation(Mockito.argThat(x -> x.getId().equals(representation.getId()))); + // Mockito.verify(updateService).updateArtifact(Mockito.argThat(x -> x.getId().equals(artifact.getId()))); + assertTrue(result.getHeader() instanceof MessageProcessedNotificationMessage); + } + + @SneakyThrows + private ResourceUpdateMessage getResourceUpdateMessage() { + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + return new ResourceUpdateMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("4.0.0") + ._issued_(xmlCalendar) + ._affectedResource_(URI.create("https://localhost:8080/resources/someId")) + .build(); + } + + @SneakyThrows + private ResourceUpdateMessage getResourceUpdateMessageWithInvalidVersion() { + final var calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + final var xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + + return new ResourceUpdateMessageBuilder() + ._senderAgent_(URI.create("https://localhost:8080")) + ._issuerConnector_(URI.create("https://localhost:8080")) + ._securityToken_(new DynamicAttributeTokenBuilder()._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build()) + ._modelVersion_("tetris") + ._issued_(xmlCalendar) + ._affectedResource_(URI.create("https://localhost:8080/someResource")) + .build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactRequestServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactRequestServiceTest.java new file mode 100644 index 000000000..d2067637c --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactRequestServiceTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ArtifactRequestMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.ArtifactRequestMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ArtifactRequestService.class}) +class ArtifactRequestServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private ArtifactRequestService requestService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> requestService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new ArtifactRequestMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setRequestedArtifact(URI.create("https://artifact")); + desc.setTransferContract(URI.create("https://transferContract")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (ArtifactRequestMessage) requestService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getRequestedArtifact(), result.getRequestedArtifact()); + assertEquals(desc.getTransferContract(), result.getTransferContract()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactResponseServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactResponseServiceTest.java new file mode 100644 index 000000000..8bbb5f808 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/ArtifactResponseServiceTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ArtifactResponseMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.ArtifactResponseMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ArtifactResponseService.class}) +class ArtifactResponseServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private ArtifactResponseService responseService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> responseService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new ArtifactResponseMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setCorrelationMessage(URI.create("https://correlationMessage")); + desc.setTransferContract(URI.create("https://transferContract")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (ArtifactResponseMessage) responseService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getCorrelationMessage(), result.getCorrelationMessage()); + assertEquals(desc.getTransferContract(), result.getTransferContract()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/ContractAgreementServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/ContractAgreementServiceTest.java new file mode 100644 index 000000000..6dc6e3d02 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/ContractAgreementServiceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ContractAgreementMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.ContractAgreementMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ContractAgreementService.class}) +class ContractAgreementServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private ContractAgreementService agreementService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> agreementService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new ContractAgreementMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setCorrelationMessage(URI.create("https://correlationMessage")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (ContractAgreementMessage) agreementService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getCorrelationMessage(), result.getCorrelationMessage()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/ContractRejectionServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/ContractRejectionServiceTest.java new file mode 100644 index 000000000..9ca67b289 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/ContractRejectionServiceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ContractRejectionMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.ContractRejectionMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ContractRejectionService.class}) +class ContractRejectionServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private ContractRejectionService rejectionService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> rejectionService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new ContractRejectionMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setCorrelationMessage(URI.create("https://correlationMessage")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (ContractRejectionMessage) rejectionService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getCorrelationMessage(), result.getCorrelationMessage()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/ContractRequestServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/ContractRequestServiceTest.java new file mode 100644 index 000000000..3f466ca5a --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/ContractRequestServiceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.ContractRequestMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.ContractRequestMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ContractRequestService.class}) +class ContractRequestServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private ContractRequestService requestService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> requestService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new ContractRequestMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setTransferContract(URI.create("https://transferContract")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (ContractRequestMessage) requestService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getTransferContract(), result.getTransferContract()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionRequestServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionRequestServiceTest.java new file mode 100644 index 000000000..8b67948cc --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionRequestServiceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DescriptionRequestMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.DescriptionRequestMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {DescriptionRequestService.class}) +class DescriptionRequestServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private DescriptionRequestService requestService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> requestService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new DescriptionRequestMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setRequestedElement(URI.create("https://requestedElement")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (DescriptionRequestMessage) requestService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getRequestedElement(), result.getRequestedElement()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionResponseServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionResponseServiceTest.java new file mode 100644 index 000000000..5a4dfa9f8 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/DescriptionResponseServiceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DescriptionResponseMessage; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.DescriptionResponseMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {DescriptionResponseService.class}) +class DescriptionResponseServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private DescriptionResponseService responseService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> responseService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new DescriptionResponseMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setCorrelationMessage(URI.create("https://correlationMessage")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (DescriptionResponseMessage) responseService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getCorrelationMessage(), result.getCorrelationMessage()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/LogMessageServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/LogMessageServiceTest.java new file mode 100644 index 000000000..ba1cad7f2 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/LogMessageServiceTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.LogMessage; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.LogMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {LogMessageService.class}) +class LogMessageServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private LogMessageService messageService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> messageService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new LogMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (LogMessage) messageService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationServiceTest.java new file mode 100644 index 000000000..2c781e2a3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/MessageProcessedNotificationServiceTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessage; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.MessageProcessedNotificationMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {MessageProcessedNotificationService.class}) +class MessageProcessedNotificationServiceTest { + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private MessageProcessedNotificationService notificationService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> notificationService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new MessageProcessedNotificationMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + desc.setCorrelationMessage(URI.create("https://correlationMessage")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (MessageProcessedNotificationMessage) notificationService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(desc.getCorrelationMessage(), result.getCorrelationMessage()); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/messages/types/NotificationServiceTest.java b/src/test/java/io/dataspaceconnector/services/messages/types/NotificationServiceTest.java new file mode 100644 index 000000000..4de0e2ae4 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/messages/types/NotificationServiceTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.messages.types; + +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.NotificationMessage; +import de.fraunhofer.iais.eis.TokenFormat; +import io.dataspaceconnector.model.messages.NotificationMessageDesc; +import io.dataspaceconnector.services.ids.ConnectorService; +import io.dataspaceconnector.services.ids.DeserializationService; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {NotificationService.class}) +class NotificationServiceTest { + + @MockBean + private ConnectorService connectorService; + + @MockBean + private IDSHttpService idsHttpService; + + @MockBean + private DeserializationService deserializationService; + + @Autowired + private NotificationService notificationService; + + @Test + public void buildMessage_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> notificationService.buildMessage(null)); + } + + @Test + public void buildMessage_validDesc_returnValidMessage() { + /* ARRANGE */ + final var desc = new NotificationMessageDesc(); + desc.setRecipient(URI.create("https://recipient")); + + final var connectorId = URI.create("https://connector"); + final var modelVersion = "4.0.0"; + final var token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + Mockito.when(connectorService.getConnectorId()).thenReturn(connectorId); + Mockito.when(connectorService.getOutboundModelVersion()).thenReturn(modelVersion); + Mockito.when(connectorService.getCurrentDat()).thenReturn(token); + + /* ACT */ + final var result = (NotificationMessage) notificationService.buildMessage(desc); + + /* ASSERT */ + assertEquals(1, result.getRecipientConnector().size()); + assertEquals(desc.getRecipient(), result.getRecipientConnector().get(0)); + assertEquals(connectorId, result.getIssuerConnector()); + assertEquals(modelVersion, result.getModelVersion()); + assertEquals(token, result.getSecurityToken()); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/ArtifactServiceTest.java b/src/test/java/io/dataspaceconnector/services/resources/ArtifactServiceTest.java new file mode 100644 index 000000000..71d31ec5d --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/ArtifactServiceTest.java @@ -0,0 +1,500 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//package io.dataspaceconnector.services.resources; +// +//import java.lang.reflect.Field; +//import java.net.URISyntaxException; +//import java.net.URL; +//import java.util.Optional; +//import java.util.UUID; +// +//import io.dataspaceconnector.exceptions.ResourceNotFoundException; +//import io.dataspaceconnector.exceptions.UnreachableLineException; +//import io.dataspaceconnector.model.Artifact; +//import io.dataspaceconnector.model.ArtifactDesc; +//import io.dataspaceconnector.model.ArtifactFactory; +//import io.dataspaceconnector.model.ArtifactImpl; +//import io.dataspaceconnector.model.Data; +//import io.dataspaceconnector.model.LocalData; +//import io.dataspaceconnector.model.QueryInput; +//import io.dataspaceconnector.model.RemoteData; +//import io.dataspaceconnector.repositories.ArtifactRepository; +//import io.dataspaceconnector.repositories.DataRepository; +//import io.dataspaceconnector.services.HttpService; +//import lombok.SneakyThrows; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.mockito.InjectMocks; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.mock.mockito.MockBean; +// +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertThrows; +//import static org.mockito.ArgumentMatchers.any; +//import static org.mockito.Mockito.never; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//@SpringBootTest(classes = {ArtifactService.class, ArtifactFactory.class, ArtifactRepository.class, +// DataRepository.class, HttpService.class}) +//class ArtifactServiceTest { +// +// @MockBean +// private ArtifactRepository artifactRepository; +// +// @MockBean +// private DataRepository dataRepository; +// +// @MockBean +// private HttpService httpService; +// +// @MockBean +// private ArtifactFactory artifactFactory; +// +// @Autowired +// @InjectMocks +// private ArtifactService service; +// +// Artifact localArtifact = getLocalArtifact(); +// +// @BeforeEach +// public void init() { +// +// } +// +// /************************************************************************** +// * persist +// *************************************************************************/ +// +// @Test +// public void persist_dataNull_persistArtifact() { +// /* ARRANGE */ +// ArtifactImpl artifact = new ArtifactImpl(); +// when(artifactRepository.saveAndFlush(artifact)).thenReturn(artifact); +// +// /* ACT */ +// service.persist(artifact); +// +// /* ASSERT */ +// verify(artifactRepository, times(1)).saveAndFlush(artifact); +// verify(dataRepository, never()).saveAndFlush(any()); +// } +// +// @SneakyThrows +// @Test +// public void persist_dataIdNull_persistDataAndArtifact() { +// /* ARRANGE */ +// ArtifactImpl artifact = new ArtifactImpl(); +// LocalData data = new LocalData(); +// +// final var dataField = artifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(artifact, data); +// +// when(artifactRepository.saveAndFlush(artifact)).thenReturn(artifact); +// when(dataRepository.saveAndFlush(data)).thenReturn(data); +// +// /* ACT */ +// service.persist(artifact); +// +// /* ASSERT */ +// verify(artifactRepository, times(1)).saveAndFlush(artifact); +// verify(dataRepository, times(1)).saveAndFlush(data); +// } +// +// @SneakyThrows +// @Test +// public void persist_dataPresentNotChanged_persistArtifact() { +// /* ARRANGE */ +// ArtifactImpl artifact = new ArtifactImpl(); +// LocalData data = new LocalData(); +// +// Long dataId = 1L; +// +// final var idField = data.getClass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(data, dataId); +// +// final var dataField = artifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(artifact, data); +// +// when(artifactRepository.saveAndFlush(artifact)).thenReturn(artifact); +// when(dataRepository.saveAndFlush(data)).thenReturn(data); +// when(dataRepository.getOne(dataId)).thenReturn(data); +// +// /* ACT */ +// service.persist(artifact); +// +// /* ASSERT */ +// verify(artifactRepository, times(1)).saveAndFlush(artifact); +// verify(dataRepository, never()).saveAndFlush(any()); +// } +// +// @SneakyThrows +// @Test +// public void persist_dataPresentAndChanged_persistDataAndArtifact() { +// /* ARRANGE */ +// // create new data instance +// LocalData data = new LocalData(); +// Long dataId = 1L; +// final var idField = data.getClass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(data, dataId); +// +// // create new artifact instance with previously created data +// ArtifactImpl artifact = new ArtifactImpl(); +// final var dataField = artifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(artifact, data); +// +// // create different data instance that will be the previously persisted data +// LocalData dataOld = new LocalData(); +// final var valueField = dataOld.getClass().getDeclaredField("value"); +// valueField.setAccessible(true); +// valueField.set(dataOld, "some value"); +// +// when(artifactRepository.saveAndFlush(artifact)).thenReturn(artifact); +// when(dataRepository.saveAndFlush(data)).thenReturn(data); +// when(dataRepository.getOne(dataId)).thenReturn(dataOld); +// +// /* ACT */ +// service.persist(artifact); +// +// /* ASSERT */ +// verify(artifactRepository, times(1)).saveAndFlush(artifact); +// verify(dataRepository, times(1)).saveAndFlush(data); +// } +// +// /************************************************************************** +// * getData. +// *************************************************************************/ +// +// @Test +// public void getData_nullArtifactId_throwsIllegalArgumentException() { +// /* ARRANGE */ +// final var queryInput = new QueryInput(); +// when(artifactRepository.findById(null)).thenThrow(new IllegalArgumentException()); +// +// /* ACT && ASSERT */ +// assertThrows(IllegalArgumentException.class, () -> service.getData(null, queryInput)); +// } +// +// @Test +// public void getData_unknownArtifactIdNullQuery_throwsResourceNotFoundException() { +// /* ARRANGE */ +// final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); +// when(artifactRepository.findById(unknownUuid)) +// .thenThrow(new ResourceNotFoundException("not found")); +// +// /* ACT && ASSERT */ +// assertThrows(ResourceNotFoundException.class, () -> service.getData(unknownUuid, (QueryInput) null)); +// } +// +// @Test +// public void getData_knownArtifactIdNullQuery_returnLocalData() { +// /* ARRANGE */ +// ArtifactImpl localArtifact = getLocalArtifact(); +// +// when(artifactRepository.findById(any())).thenReturn(Optional.of(localArtifact)); +// when(artifactFactory.create(any())).thenReturn(localArtifact); +// when(dataRepository.getOne(any())).thenReturn(getLocalData()); +// +// /* ACT */ +// final var data = service.getData(localArtifact.getId(), (QueryInput) null); +// +// /* ASSERT */ +// assertEquals(getLocalData().getValue(), (String) data); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdNullQuery_increaseAccessCounter() { +// /* ARRANGE */ +// ArtifactImpl localArtifact = getLocalArtifact(); +// +// when(artifactRepository.findById(any())).thenReturn(Optional.of(localArtifact)); +// when(artifactFactory.create(any())).thenReturn(localArtifact); +// when(dataRepository.getOne(any())).thenReturn(getLocalData()); +// +// final var before = localArtifact.getNumAccessed(); +// +// /* ACT */ +// service.getData(localArtifact.getId(), (QueryInput) null); +// +// /* ASSERT */ +// Field numAccessedField = Artifact.class.getDeclaredField("numAccessed"); +// numAccessedField.setAccessible(true); +// numAccessedField.set(localArtifact, before + 1); +// +// verify(artifactRepository, times(1)).saveAndFlush(localArtifact); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdBasicAuthNullQuery_returnRemoteData() { +// /* ARRANGE */ +// String remoteData = "I am data from a remote source."; +// ArtifactImpl remoteArtifact = getRemoteArtifact(getRemoteDataWithBasicAuth()); +// URL url = ((RemoteData) remoteArtifact.getData()).getAccessUrl(); +// String username = ((RemoteData) remoteArtifact.getData()).getUsername(); +// String password = ((RemoteData) remoteArtifact.getData()).getPassword(); +// +// when(artifactRepository.findById(remoteArtifact.getId())) +// .thenReturn(Optional.of(remoteArtifact)); +// when(httpService.sendHttpsGetRequestWithBasicAuth(url.toString(), username, password, null)) +// .thenReturn(remoteData); +// +// /* ACT */ +// final var data = service.getData(remoteArtifact.getId(), (QueryInput) null); +// +// /* ASSERT */ +// assertEquals(remoteData, data); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdNoBasicAuthNullQuery_returnRemoteData() { +// /* ARRANGE */ +// String remoteData = "I am data from a remote source."; +// ArtifactImpl remoteArtifact = getRemoteArtifact(getRemoteData()); +// URL url = ((RemoteData) remoteArtifact.getData()).getAccessUrl(); +// +// when(artifactRepository.findById(remoteArtifact.getId())) +// .thenReturn(Optional.of(remoteArtifact)); +// when(httpService.sendHttpsGetRequest(url.toString(), null)).thenReturn(remoteData); +// +// /* ACT */ +// final var data = service.getData(remoteArtifact.getId(),(QueryInput) null); +// +// /* ASSERT */ +// assertEquals(remoteData, data); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdNoBasicAuthWithQuery_returnRemoteData() { +// /* ARRANGE */ +// String remoteData = "I am data from a remote source."; +// ArtifactImpl remoteArtifact = getRemoteArtifact(getRemoteData()); +// URL url = ((RemoteData) remoteArtifact.getData()).getAccessUrl(); +// QueryInput queryInput = getQueryInput(); +// +// when(artifactRepository.findById(remoteArtifact.getId())) +// .thenReturn(Optional.of(remoteArtifact)); +// when(httpService.sendHttpsGetRequest(url.toString(), queryInput)).thenReturn(remoteData); +// +// /* ACT */ +// final var data = service.getData(remoteArtifact.getId(), queryInput); +// +// /* ASSERT */ +// assertEquals(remoteData, data); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdNoBasicAuthWithQueryMalformedUrl_throwRuntimeException() { +// /* ARRANGE */ +// String remoteData = "I am data from a remote source."; +// String expectedExceptionMessage = "Could not connect to data source."; //from ArtifactService +// ArtifactImpl remoteArtifact = getRemoteArtifact(getRemoteData()); +// URL url = ((RemoteData) remoteArtifact.getData()).getAccessUrl(); +// QueryInput queryInput = getQueryInput(); +// +// when(artifactRepository.findById(remoteArtifact.getId())) +// .thenReturn(Optional.of(remoteArtifact)); +// when(httpService.sendHttpsGetRequest(url.toString(), queryInput)) +// .thenThrow(new URISyntaxException("input", "reason")); +// +// /* ACT && ASSERT */ +// assertThrows(RuntimeException.class, () -> service.getData(remoteArtifact.getId(), queryInput), +// expectedExceptionMessage); +// } +// +// @SneakyThrows +// @Test +// public void getData_knownArtifactIdNoBasicAuthWithQuery_throwRuntimeException() { +// /* ARRANGE */ +// String remoteData = "I am data from a remote source."; +// String expectedExceptionMessage = "Exception message"; +// ArtifactImpl remoteArtifact = getRemoteArtifact(getRemoteData()); +// URL url = ((RemoteData) remoteArtifact.getData()).getAccessUrl(); +// QueryInput queryInput = getQueryInput(); +// +// when(artifactRepository.findById(remoteArtifact.getId())) +// .thenReturn(Optional.of(remoteArtifact)); +// when(httpService.sendHttpsGetRequest(url.toString(), queryInput)) +// .thenThrow(new RuntimeException(expectedExceptionMessage)); +// +// /* ACT && ASSERT */ +// assertThrows(RuntimeException.class, () -> service.getData(remoteArtifact.getId(), queryInput), +// expectedExceptionMessage); +// } +// +// @SneakyThrows +// @Test +// public void getData_unknownDataType_throwNotImplementedException() { +// /* ARRANGE */ +// ArtifactImpl unknownArtifact = getUnknownArtifact(); +// final var dataField = unknownArtifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(unknownArtifact, new UnknownData()); +// +// when(artifactRepository.findById(unknownArtifact.getId())) +// .thenReturn(Optional.of(unknownArtifact)); +// +// /* ACT && ASSERT */ +// assertThrows(UnreachableLineException.class, +// () -> service.getData(unknownArtifact.getId(), (QueryInput) null)); +// } +// +// /************************************************************************** +// * Utilities. +// *************************************************************************/ +// +// private ArtifactDesc getLocalArtifactDesc() { +// final var desc = new ArtifactDesc(); +// desc.setTitle("LocalArtifact"); +// desc.setValue("Random Value"); +// +// return desc; +// } +// +// @SneakyThrows +// private ArtifactImpl getLocalArtifact() { +// final var artifactConstructor = ArtifactImpl.class.getConstructor(); +// artifactConstructor.setAccessible(true); +// +// final var artifact = artifactConstructor.newInstance(); +// +// final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); +// titleField.setAccessible(true); +// titleField.set(artifact, "LocalArtifact"); +// +// final var idField = +// artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(artifact, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); +// +// final var dataField = artifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(artifact, getLocalData()); +// +// return artifact; +// } +// +// @SneakyThrows +// private ArtifactImpl getUnknownArtifact() { +// final var artifactConstructor = ArtifactImpl.class.getConstructor(); +// artifactConstructor.setAccessible(true); +// +// final var artifact = artifactConstructor.newInstance(); +// +// final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); +// titleField.setAccessible(true); +// titleField.set(artifact, "LocalArtifact"); +// +// final var idField = +// artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(artifact, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); +// +// return artifact; +// } +// +// @SneakyThrows +// private LocalData getLocalData() { +// final var dataConstructor = LocalData.class.getConstructor(); +// dataConstructor.setAccessible(true); +// +// final var localData = dataConstructor.newInstance(); +// +// final var idField = localData.getClass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(localData, Long.valueOf(1)); +// +// final var valueField = localData.getClass().getDeclaredField("value"); +// valueField.setAccessible(true); +// valueField.set(localData, getLocalArtifactDesc().getValue()); +// +// return localData; +// } +// +// @SneakyThrows +// private RemoteData getRemoteData() { +// final var remoteData = new RemoteData(); +// +// Field accessUrlField = remoteData.getClass().getDeclaredField("accessUrl"); +// accessUrlField.setAccessible(true); +// accessUrlField.set(remoteData, new URL("http://some-url.com")); +// +// return remoteData; +// } +// +// @SneakyThrows +// private RemoteData getRemoteDataWithBasicAuth() { +// final var remoteData = new RemoteData(); +// +// Field accessUrlField = remoteData.getClass().getDeclaredField("accessUrl"); +// accessUrlField.setAccessible(true); +// accessUrlField.set(remoteData, new URL("http://some-url.com")); +// +// Field usernameField = remoteData.getClass().getDeclaredField("username"); +// usernameField.setAccessible(true); +// usernameField.set(remoteData, "username"); +// +// Field passwordField = remoteData.getClass().getDeclaredField("password"); +// passwordField.setAccessible(true); +// passwordField.set(remoteData, "password"); +// +// return remoteData; +// } +// +// @SneakyThrows +// private ArtifactImpl getRemoteArtifact(RemoteData remoteData) { +// final var artifactConstructor = ArtifactImpl.class.getConstructor(); +// artifactConstructor.setAccessible(true); +// +// final var artifact = artifactConstructor.newInstance(); +// +// final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); +// titleField.setAccessible(true); +// titleField.set(artifact, "RemoteArtifact"); +// +// final var idField = +// artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); +// idField.setAccessible(true); +// idField.set(artifact, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); +// +// final var dataField = artifact.getClass().getDeclaredField("data"); +// dataField.setAccessible(true); +// dataField.set(artifact, remoteData); +// +// return artifact; +// } +// +// private QueryInput getQueryInput() { +// QueryInput queryInput = new QueryInput(); +// queryInput.getParams().put("paramName", "paramValue"); +// return queryInput; +// } +// +// private class UnknownData extends Data{ +// +// } +//} diff --git a/src/test/java/io/dataspaceconnector/services/resources/BasePathTest.java b/src/test/java/io/dataspaceconnector/services/resources/BasePathTest.java new file mode 100644 index 000000000..38c817137 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/BasePathTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +class BasePathTest { + + /*@Test + void toString_nothing_correctPath() { + *//* ARRANGE *//* + final var input = BasePath.OFFERS; + + *//* ACT *//* + final var host = Mockito.when(EndpointUtils.getCurrentBasePathString()).thenReturn("https://host"); + final var basePath = input.toString(); + final var path = host + basePath; + + *//* ASSERT *//* + assertEquals(path, basePath); + }*/ +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/CatalogOfferedResourceLinkerTest.java b/src/test/java/io/dataspaceconnector/services/resources/CatalogOfferedResourceLinkerTest.java new file mode 100644 index 000000000..dc86fb87d --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/CatalogOfferedResourceLinkerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.OfferedResource; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {CatalogOfferedResourceLinker.class}) +class CatalogOfferedResourceLinkerTest { + + @MockBean + CatalogService catalogService; + + @MockBean + OfferedResourceService resourceService; + + @Autowired + @InjectMocks + CatalogOfferedResourceLinker linker; + + Catalog catalog = getCatalog(); + OfferedResource resource = getResource(); + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnOfferedResources() { + /* ARRANGE */ + catalog.getOfferedResources().add(resource); + + /* ACT */ + final var resources = linker.getInternal(catalog); + + /* ASSERT */ + final var expected = List.of(resource); + assertEquals(expected, resources); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private Catalog getCatalog() { + final var constructor = Catalog.class.getConstructor(); + constructor.setAccessible(true); + + final var catalog = constructor.newInstance(); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, "Catalog"); + + final var offeredResourcesField = catalog.getClass().getDeclaredField("offeredResources"); + offeredResourcesField.setAccessible(true); + offeredResourcesField.set(catalog, new ArrayList()); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return catalog; + } + + @SneakyThrows + private OfferedResource getResource() { + final var constructor = OfferedResource.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var resource = constructor.newInstance(); + + final var titleField = resource.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(resource, "Hello"); + + final var idField = + resource.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return resource; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/CatalogRequestedResourceLinkerTest.java b/src/test/java/io/dataspaceconnector/services/resources/CatalogRequestedResourceLinkerTest.java new file mode 100644 index 000000000..555f39b64 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/CatalogRequestedResourceLinkerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.RequestedResource; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {CatalogRequestedResourceLinker.class}) +class CatalogRequestedResourceLinkerTest { + + @MockBean + CatalogService catalogService; + + @MockBean + RequestedResourceService resourceService; + + @Autowired + @InjectMocks + CatalogRequestedResourceLinker linker; + + Catalog catalog = getCatalog(); + RequestedResource resource = getResource(); + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnOfferedResources() { + /* ARRANGE */ + catalog.getRequestedResources().add(resource); + + /* ACT */ + final var resources = linker.getInternal(catalog); + + /* ASSERT */ + final var expected = List.of(resource); + assertEquals(expected, resources); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private Catalog getCatalog() { + final var constructor = Catalog.class.getConstructor(); + constructor.setAccessible(true); + + final var catalog = constructor.newInstance(); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, "Catalog"); + + final var requestedResourcesField = catalog.getClass().getDeclaredField("requestedResources"); + requestedResourcesField.setAccessible(true); + requestedResourcesField.set(catalog, new ArrayList()); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return catalog; + } + + @SneakyThrows + private RequestedResource getResource() { + final var constructor = RequestedResource.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var resource = constructor.newInstance(); + + final var titleField = resource.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(resource, "Hello"); + + final var idField = + resource.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return resource; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceTest.java b/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceTest.java new file mode 100644 index 000000000..e67fe3fbf --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceTest.java @@ -0,0 +1,395 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.CatalogFactory; +import io.dataspaceconnector.repositories.CatalogRepository; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.AdditionalMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {CatalogService.class}) +class CatalogServiceTest { + @MockBean + private CatalogFactory factory; + + @MockBean + private CatalogRepository repository; + + @Autowired + @InjectMocks + private CatalogService service; + + CatalogDesc catalogOneDesc = getCatalogOneDesc(); + CatalogDesc catalogTwoDesc = getCatalogTwoDesc(); + Catalog catalogOne = getCatalogOne(); + Catalog catalogTwo = getCatalogTwo(); + + List catalogList = new ArrayList<>(); + + /************************************************************************** + * Setup + *************************************************************************/ + + @BeforeEach + public void init() { + Mockito.when(factory.create(catalogOneDesc)).thenReturn(catalogOne); + Mockito.when(factory.create(catalogTwoDesc)).thenReturn(catalogTwo); + Mockito.when(repository.findById(Mockito.eq(catalogOne.getId()))) + .thenReturn(Optional.of(catalogOne)); + Mockito.when(repository.findById(Mockito.eq(catalogTwo.getId()))) + .thenReturn(Optional.of(catalogTwo)); + Mockito.when(repository.saveAndFlush(Mockito.eq(catalogOne))) + .thenReturn(catalogOne); + Mockito.when(repository.saveAndFlush(Mockito.eq(catalogTwo))) + .thenReturn(catalogTwo); + + Mockito.when(repository.saveAndFlush(Mockito.any())).thenAnswer(this::saveAndFlushMock); + Mockito.when(repository.findById(AdditionalMatchers.not(Mockito.eq(catalogOne.getId())))) + .thenReturn(Optional.empty()); + Mockito.when(repository.findById(Mockito.isNull())) + .thenThrow(InvalidDataAccessApiUsageException.class); + Mockito.when(repository.findAll(Pageable.unpaged())).thenAnswer(this::findAllMock); + Mockito.doThrow(InvalidDataAccessApiUsageException.class) + .when(repository) + .deleteById(Mockito.isNull()); + Mockito.doAnswer(this::deleteByIdMock).when(repository).deleteById(Mockito.isA(UUID.class)); + } + + private static Page toPage( final List catalogList, final Pageable pageable ) { + return new PageImpl<>( + catalogList.subList(0, catalogList.size()), pageable, catalogList.size()); + } + + private Page findAllMock( final InvocationOnMock invocation ) { + return toPage(catalogList, invocation.getArgument(0)); + } + + @SneakyThrows + private Catalog saveAndFlushMock( final InvocationOnMock invocation ) { + final var obj = (Catalog) invocation.getArgument(0); + final var idField = obj.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(obj, UUID.randomUUID()); + + catalogList.add(obj); + return obj; + } + + private Answer deleteByIdMock( final InvocationOnMock invocation ) { + final var obj = (UUID) invocation.getArgument(0); + catalogList.removeIf(x -> x.getId().equals(obj)); + return null; + } + + /************************************************************************** + * create + *************************************************************************/ + + @Test + public void create_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.create(null)); + } + + @Test + public void create_ValidDesc_returnCatalog() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var catalog = service.create(catalogOneDesc); + + /* ACT && ASSERT */ + assertNotNull(catalog); + } + + @Test + public void create_ValidDesc_returnHasId() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var catalog = service.create(catalogOneDesc); + + /* ACT && ASSERT */ + assertEquals(catalogOne, catalog); + } + + @Test + public void create_ValidDesc_createOnlyOneCatalog() { + /* ARRANGE */ + final var beforeCount = service.getAll(Pageable.unpaged()).getSize(); + + /* ACT */ + service.create(catalogOneDesc); + + /* ASSERT */ + assertEquals(beforeCount + 1, service.getAll(Pageable.unpaged()).getSize()); + } + + /************************************************************************** + * update + *************************************************************************/ + + @Test + public void update_nullDesc_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows( + IllegalArgumentException.class, () -> service.update(catalogOne.getId(), null)); + } + + @Test + public void update_nullId_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.update(null, catalogOneDesc)); + } + + @Test + public void update_unknownId_throwResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, () -> service.get(unknownUuid)); + } + + /************************************************************************** + * get + *************************************************************************/ + + @Test + public void get_nullId_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.get(null)); + } + + @Test + public void get_knownId_returnCatalog() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertNotNull(service.get(catalogOne.getId())); + } + + @Test + public void get_unknownId_throwResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + final var msg = assertThrows(ResourceNotFoundException.class, () -> service.get(unknownUuid)); + assertEquals(service.getClass().getSimpleName() + ": " + unknownUuid.toString(), msg.getMessage()); + } + + /************************************************************************** + * getAll + *************************************************************************/ + + @Test + public void getAll_null_throwsIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.getAll(null)); + } + + /************************************************************************** + * doesExist + *************************************************************************/ + + @Test + public void doesExist_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.doesExist(null)); + } + + @Test + public void doesExist_knownId_returnTrue() { + /* ARRANGE */ + final var knownUuid = catalogOne.getId(); + + /* ACT && ASSERT */ + assertTrue(service.doesExist(knownUuid)); + } + + @Test + public void doesExist_unknownId_returnFalse() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertFalse(service.doesExist(unknownUuid)); + } + + + /************************************************************************** + * delete + *************************************************************************/ + + @Test + public void delete_nullId_throwsIllegalArgumentException() { + /* ARRANGE */ + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> service.delete(null)); + } + + @Test + public void delete_knownId_removedObject() { + /* ARRANGE */ + final var id = service.create(catalogOneDesc); + service.create(catalogTwoDesc); + + final var beforeCount = service.getAll(Pageable.unpaged()).getSize(); + + /* ACT */ + service.delete(id.getId()); + + /* ASSERT */ + assertEquals(beforeCount - 1, service.getAll(Pageable.unpaged()).getSize()); + } + + @Test + public void delete_knownId_removedObjectWithId() { + /* ARRANGE */ + final var id = service.create(catalogOneDesc); + service.create(catalogTwoDesc); + + /* ACT */ + service.delete(id.getId()); + + /* ASSERT */ + assertEquals(0, (int) service.getAll(Pageable.unpaged()) + .stream() + .filter(x -> x.getId().equals(id.getId())).count()); + } + + @Test + public void delete_unknownId_removedObject() { + /* ARRANGE */ + final var id = service.create(catalogOneDesc); + service.create(catalogTwoDesc); + + final var beforeCount = service.getAll(Pageable.unpaged()).getSize(); + + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT */ + service.delete(unknownUuid); + + /* ASSERT */ + assertEquals(beforeCount, service.getAll(Pageable.unpaged()).getSize()); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + private CatalogDesc getCatalogOneDesc() { + var desc = new CatalogDesc(); + desc.setTitle("The new title."); + + return desc; + } + + private CatalogDesc getCatalogTwoDesc() { + var desc = new CatalogDesc(); + desc.setTitle("The different title."); + + return desc; + } + + @SneakyThrows + private Catalog getCatalogOne() { + final var desc = getCatalogOneDesc(); + + final var catalogConstructor = Catalog.class.getConstructor(); + + final var catalog = catalogConstructor.newInstance(); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, desc.getTitle()); + + return catalog; + } + + @SneakyThrows + private Catalog getCatalogTwo() { + final var desc = getCatalogTwoDesc(); + + final var catalogConstructor = Catalog.class.getConstructor(); + + final var catalog = catalogConstructor.newInstance(); + + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, UUID.fromString("afb43170-b8d4-4872-b923-3490de99a53b")); + + final var titleField = catalog.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(catalog, desc.getTitle()); + + return catalog; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceUpdateTest.java b/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceUpdateTest.java new file mode 100644 index 000000000..6f5fe61e2 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/CatalogServiceUpdateTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.Optional; +import java.util.UUID; + +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.CatalogFactory; +import io.dataspaceconnector.repositories.CatalogRepository; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest( classes = { CatalogService.class } ) +public class CatalogServiceUpdateTest { + + @SpyBean + private CatalogFactory factory; + + @MockBean + private CatalogRepository repository; + + Catalog newCatalog; + Catalog updatedCatalog; + + UUID validId = UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e"); + + @Autowired + @InjectMocks + private CatalogService service; + + @BeforeEach + public void init() { + newCatalog = getCatalogFromValidDesc(validId, getNewCatalog(getValidDesc())); + updatedCatalog = getCatalogFromValidDesc(validId, getNewCatalog(getUpdatedValidDesc())); + + Mockito.when(repository.saveAndFlush(Mockito.eq(newCatalog))).thenReturn(newCatalog); + Mockito.when(repository.saveAndFlush(Mockito.eq(updatedCatalog))).thenReturn(updatedCatalog); + Mockito.when(repository.findById(Mockito.eq(newCatalog.getId()))).thenReturn(Optional.of(newCatalog)); + } + + @Test + public void update_sameDesc_returnSameCatalog() { + /* ARRANGE */ + final var before = getCatalogFromValidDesc(validId, getNewCatalog(getValidDesc())); + + /* ACT */ + final var after = service.update(newCatalog.getId(), getValidDesc()); + + /* ASSERT */ + assertEquals(before, after); + } + + @Test + public void update_updateDesc_returnUpdatedCatalog() { + /* ARRANGE */ + final var shouldLookLike = getCatalogFromValidDesc(validId, getNewCatalog(getUpdatedValidDesc())); + + /* ACT */ + final var after = service.update(validId, getUpdatedValidDesc()); + + /* ASSERT */ + assertEquals(after, shouldLookLike); + } + + @Test + public void update_sameDesc_notUpdatedDbCatalog() { + /* ARRANGE */ + service.update(validId, getValidDesc()); + + /* ACT */ + Mockito.verify(repository, Mockito.never()).saveAndFlush(Mockito.any()); + } + + @Test + public void update_updatedDesc_UpdatedDbCatalog() { + /* ARRANGE */ + service.update(validId, getUpdatedValidDesc()); + + /* ACT */ + Mockito.verify(repository, Mockito.atLeastOnce()).saveAndFlush(Mockito.eq(updatedCatalog)); + } + + private CatalogDesc getValidDesc() { + var desc = new CatalogDesc(); + desc.setDescription("The new description."); + desc.setTitle("The new title."); + + return desc; + } + + private CatalogDesc getUpdatedValidDesc() { + var desc = new CatalogDesc(); + desc.setDescription("Something different."); + desc.setTitle("The new title."); + + return desc; + } + + private Catalog getNewCatalog( final CatalogDesc desc ) { + return factory.create(desc); + } + + @SneakyThrows + private Catalog getCatalogFromValidDesc( final UUID id, final Catalog catalog ) { + final var idField = catalog.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(catalog, id); + + return catalog; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/ContractRuleLinkerTest.java b/src/test/java/io/dataspaceconnector/services/resources/ContractRuleLinkerTest.java new file mode 100644 index 000000000..f57afad70 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/ContractRuleLinkerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractRule; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {RelationServices.ContractRuleLinker.class}) +class ContractRuleLinkerTest { + @MockBean + ContractService contractService; + + @MockBean + RuleService ruleService; + + @Autowired + @InjectMocks + RelationServices.ContractRuleLinker linker; + + Contract contract = getContract(); + ContractRule rule = getRule(); + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnOfferedResources() { + /* ARRANGE */ + contract.getRules().add(rule); + + /* ACT */ + final var resources = linker.getInternal(contract); + + /* ASSERT */ + final var expected = List.of(rule); + assertEquals(expected, resources); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private Contract getContract() { + final var constructor = Contract.class.getConstructor(); + constructor.setAccessible(true); + + final var contract = constructor.newInstance(); + + final var titleField = contract.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(contract, "Catalog"); + + final var rulesField = contract.getClass().getDeclaredField("rules"); + rulesField.setAccessible(true); + rulesField.set(contract, new ArrayList()); + + final var idField = contract.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(contract, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return contract; + } + + @SneakyThrows + private ContractRule getRule() { + final var constructor = ContractRule.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var rule = constructor.newInstance(); + + final var titleField = rule.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(rule, "Hello"); + + final var idField = rule.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(rule, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return rule; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceContractLinkerTest.java b/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceContractLinkerTest.java new file mode 100644 index 000000000..f840bb533 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceContractLinkerTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {OfferedResourceContractLinker.class}) +class OfferedResourceContractLinkerTest { + @MockBean + ResourceService resourceService; + + @MockBean + ContractService contractService; + + @Autowired + @InjectMocks + OfferedResourceContractLinker linker; + + OfferedResource resource = getResource(); + Contract contract = getContract(); + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnResources() { + /* ARRANGE */ + resource.getContracts().add(contract); + + /* ACT */ + final var contracts = linker.getInternal(resource); + + /* ASSERT */ + final var expected = List.of(contract); + assertEquals(expected, contracts); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private OfferedResource getResource() { + final var constructor = OfferedResource.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var resource = constructor.newInstance(); + + final var titleField = resource.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(resource, "Hello"); + + final var contractsField = + resource.getClass().getSuperclass().getDeclaredField("contracts"); + contractsField.setAccessible(true); + contractsField.set(resource, new ArrayList()); + + final var idField = + resource.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return resource; + } + + @SneakyThrows + private Contract getContract() { + final var constructor = Contract.class.getConstructor(); + constructor.setAccessible(true); + + final var contract = constructor.newInstance(); + + final var titleField = contract.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(contract, "Contract"); + + final var idField = contract.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(contract, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return contract; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceRepresentationTest.java b/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceRepresentationTest.java new file mode 100644 index 000000000..a63cc5f5f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/OfferedResourceRepresentationTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.Representation; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {OfferedResourceRepresentation.class}) +class OfferedResourceRepresentationTest { + + @MockBean + OfferedResourceService resourceService; + + @MockBean + RepresentationService representationService; + + @Autowired + @InjectMocks + OfferedResourceRepresentation linker; + + OfferedResource resource = getResource(); + Representation representation = getRepresentation(); + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnRepresentation() { + /* ARRANGE */ + resource.getRepresentations().add(representation); + + /* ACT */ + final var representations = linker.getInternal(resource); + + /* ASSERT */ + final var expected = List.of(representation); + assertEquals(expected, representations); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private OfferedResource getResource() { + final var constructor = OfferedResource.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var resource = constructor.newInstance(); + + final var titleField = resource.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(resource, "Hello"); + + final var representationField = + resource.getClass().getSuperclass().getDeclaredField("representations"); + representationField.setAccessible(true); + representationField.set(resource, new ArrayList()); + + final var idField = + resource.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(resource, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return resource; + } + + @SneakyThrows + private Representation getRepresentation() { + final var constructor = Representation.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final var representation = constructor.newInstance(); + + final var titleField = representation.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(representation, "Hello"); + + final var idField = representation.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + return representation; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/RepresentationArtifactLinkerTest.java b/src/test/java/io/dataspaceconnector/services/resources/RepresentationArtifactLinkerTest.java new file mode 100644 index 000000000..5395f2277 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/RepresentationArtifactLinkerTest.java @@ -0,0 +1,730 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import io.dataspaceconnector.exceptions.ResourceNotFoundException; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactImpl; +import io.dataspaceconnector.model.Representation; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.AdditionalMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Pageable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {RelationServices.RepresentationArtifactLinker.class}) +public class RepresentationArtifactLinkerTest { + @MockBean + RepresentationService representationService; + + @MockBean + ArtifactService artifactService; + + @Autowired + @InjectMocks + RelationServices.RepresentationArtifactLinker linker; + + Representation representation = getRepresentation(); + Artifact artifactOne = getArtifactOne(); + Artifact artifactTwo = getArtifactTwo(); + Artifact artifactThree = getArtifactThree(); + + @BeforeEach + public void init() { + Mockito.when(representationService.get(Mockito.eq(representation.getId()))) + .thenReturn(representation); + Mockito.when(representationService.get(AdditionalMatchers.not(AdditionalMatchers.and( + Mockito.notNull(), Mockito.eq(representation.getId()))))) + .thenThrow(ResourceNotFoundException.class); + Mockito.when(representationService.persist(Mockito.any())) + .thenAnswer((Answer) + invocationOnMock -> invocationOnMock.getArgument(0)); + + Mockito.when(artifactService.get(Mockito.eq(artifactOne.getId()))).thenReturn(artifactOne); + Mockito.when(artifactService.get(Mockito.eq(artifactTwo.getId()))).thenReturn(artifactTwo); + Mockito.when(artifactService.get(Mockito.eq(artifactThree.getId()))) + .thenReturn(artifactThree); + + Mockito.when(artifactService.doesExist(Mockito.eq(artifactOne.getId()))).thenReturn(true); + Mockito.when(artifactService.doesExist(Mockito.eq(artifactTwo.getId()))).thenReturn(true); + Mockito.when(artifactService.doesExist(Mockito.eq(artifactThree.getId()))).thenReturn(true); + } + + /************************************************************************** + * get + *************************************************************************/ + + @Test + public void get_unknownId_throwResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows( + ResourceNotFoundException.class, () -> linker.get(unknownUuid, Pageable.unpaged())); + } + + @Test + public void get_knownId_notNull() { + /* ARRANGE */ + linker.add(representation.getId(), + Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ACT */ + final var linkedArtifacts = linker.get(representation.getId(), Pageable.unpaged()); + + /* ASSERT */ + assertNotNull(linkedArtifacts); + } + + @Test + public void get_nullId_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.get(null, Pageable.unpaged())); + } + + @Test + public void get_nullPageable_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows( + IllegalArgumentException.class, () -> linker.get(representation.getId(), null)); + } + + @Test + public void get_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.get(null, null)); + } + + @Test + public void get_knownId_getArtifacts() { + /* ARRANGE */ + linker.add(representation.getId(), + Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ACT */ + final var linkedArtifacts = linker.get(representation.getId(), Pageable.unpaged()).toList(); + + /* ASSERT */ + assertTrue(linkedArtifacts.contains(artifactOne)); + assertTrue(linkedArtifacts.contains(artifactTwo)); + assertTrue(linkedArtifacts.contains(artifactThree)); + } + + /************************************************************************** + * add + *************************************************************************/ + + @Test + public void add_nullId_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> linker.add(null, Set.of(artifactOne.getId()))); + } + + @Test + public void add_knownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.add(knownId, null)); + } + + @Test + public void add_unknownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.add(unknownUuid, null)); + } + + @Test + + public void add_null_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.add(null, null)); + } + + @Test + public void add_unknownId_throwsResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.add(unknownUuid, Set.of(artifactOne.getId()))); + } + + @Test + public void add_knownIdEmptyEntities_noOwnerServiceActions() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT */ + linker.add(knownId, Set.of()); + + /* ASSERT */ + Mockito.verifyNoInteractions(representationService, artifactService); + } + + @Test + public void add_knownIdSetWithUnknownEntities_throwsResourceNotFoundException() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.add(knownId, Set.of(artifactOne.getId(), unknownUuid))); + } + + @Test + public void add_knownIdSetWithUnknownEntities_doNotModify() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + try { + /* ACT */ + linker.add(knownId, Set.of(artifactOne.getId(), unknownUuid)); + } catch (ResourceNotFoundException exception) { + /* ASSERT */ + assertEquals(0, linker.get(knownId, Pageable.unpaged()).getTotalElements()); + } + } + + @Test + public void add_knownIdSetWithNullAndValidEntities_throwsNullPointerException() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, + () -> linker.add(knownId, Set.of(artifactOne.getId(), null))); + } + + @Test + public void add_knownEntities_createRelation() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT */ + linker.add( + knownId, Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ASSERT */ + final var elements = linker.get(knownId, Pageable.unpaged()).toList(); + assertTrue(elements.contains(artifactOne)); + assertTrue(elements.contains(artifactTwo)); + assertTrue(elements.contains(artifactThree)); + } + + @Test + public void add_knownEntities_createRelationOneOfEach() { + /* ARRANGE */ + final var knownId = representation.getId(); + + final var before = linker.get(knownId, Pageable.unpaged()).toList().size(); + + /* ACT */ + linker.add( + knownId, Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ASSERT */ + final var after = linker.get(knownId, Pageable.unpaged()).toList().size(); + assertEquals(before + 3, after); + } + + @Test + public void add_knownEntitiesAlreadyExists_createOnlyMissingRelations() { + /* ARRANGE */ + final var knownId = representation.getId(); + + linker.add(knownId, Set.of(artifactOne.getId(), artifactThree.getId())); + + final var before = linker.get(knownId, Pageable.unpaged()).toList().size(); + + /* ACT */ + linker.add( + knownId, Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ASSERT */ + final var after = linker.get(knownId, Pageable.unpaged()).toList().size(); + assertEquals(before + 1, after); + } + + @Test + public void add_validInput_isPersisted() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT */ + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ASSERT */ + Mockito.verify(representationService, Mockito.atLeast(1)) + .persist(Mockito.eq(representation)); + } + + /************************************************************************** + * remove + *************************************************************************/ + + @Test + public void remove_nullId_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> linker.remove(null, Set.of(artifactOne.getId()))); + } + + @Test + public void remove_knownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.remove(knownId, null)); + } + + @Test + public void remove_unknownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.remove(unknownUuid, null)); + } + + @Test + public void remove_null_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.remove(null, null)); + } + + @Test + public void remove_unknownId_throwsResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.remove(unknownUuid, Set.of(artifactOne.getId()))); + } + + @Test + public void remove_knownIdEmptyEntities_noOwnerServiceActions() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT */ + linker.remove(knownId, Set.of()); + + /* ASSERT */ + Mockito.verifyNoInteractions(representationService, artifactService); + } + + @Test + public void remove_knownIdSetWithUnknownEntities_throwsResourceNotFoundException() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT */ + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.remove(knownId, Set.of(artifactOne.getId(), unknownUuid))); + } + + @Test + public void remove_knownIdSetWithUnknownEntities_doNotModify() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + linker.add(knownId, Set.of(artifactOne.getId())); + + try { + /* ACT */ + linker.remove(knownId, Set.of(artifactOne.getId(), unknownUuid)); + } catch (ResourceNotFoundException exception) { + /* ASSERT */ + assertEquals(1, linker.get(knownId, Pageable.unpaged()).getTotalElements()); + } + } + + @Test + public void remove_knownEntities_removeRelation() { + /* ARRANGE */ + final var knownId = representation.getId(); + + linker.add( + knownId, Set.of(artifactOne.getId(), artifactTwo.getId(), artifactThree.getId())); + + /* ACT */ + linker.remove(knownId, Set.of(artifactOne.getId(), artifactTwo.getId())); + + /* ASSERT */ + final var elements = linker.get(knownId, Pageable.unpaged()).toList(); + assertTrue(elements.contains(artifactThree)); + assertEquals(1, elements.size()); + } + + @Test + public void remove_validInput_isPersisted() { + /* ARRANGE */ + final var knownId = representation.getId(); + linker.add(knownId, Set.of(artifactOne.getId())); + + Mockito.verify(representationService, Mockito.times(1)).persist(Mockito.eq(representation)); + + /* ACT */ + linker.remove(knownId, Set.of(artifactOne.getId())); + + /* ASSERT */ + Mockito.verify(representationService, Mockito.atLeast(2)) + .persist(Mockito.eq(representation)); + } + + @Test + public void remove_knownIdSetWithNullAndValidEntities_throwsNullPointerException() { + /* ARRANGE */ + final var knownId = representation.getId(); + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, + () -> linker.remove(knownId, Set.of(artifactOne.getId(), null))); + } + + /************************************************************************** + * replace + *************************************************************************/ + + @Test + public void replace_nullId_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> linker.replace(null, Set.of(artifactOne.getId()))); + } + + @Test + public void replace_knownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var knownId = representation.getId(); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.replace(knownId, null)); + } + + @Test + public void replace_unknownIdNullEntities_throwsIllegalArgumentsException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.replace(unknownUuid, null)); + } + + @Test + public void replace_null_throwsIllegalArgumentsException() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> linker.replace(null, null)); + } + + @Test + public void replace_unknownId_throwsResourceNotFoundException() { + /* ARRANGE */ + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.remove(unknownUuid, Set.of(artifactOne.getId()))); + } + + @Test + public void replace_knownIdEmptySet_throwsResourceNotFoundException() { + /* ARRANGE */ + final var knownId = representation.getId(); + + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ACT */ + linker.replace(knownId, Set.of()); + + /* ASSERT */ + final var numElements = linker.get(knownId, Pageable.unpaged()).getTotalElements(); + assertEquals(0, numElements); + } + + @Test + public void replace_knownIdSetWithUnknownEntities_throwsResourceNotFoundException() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ACT && ASSERT */ + assertThrows(ResourceNotFoundException.class, + () -> linker.replace(knownId, Set.of(artifactTwo.getId(), unknownUuid))); + } + + @Test + public void replace_knownIdSetWithUnknownEntities_doNotModify() { + /* ARRANGE */ + final var knownId = representation.getId(); + final var unknownUuid = UUID.fromString("550e8400-e29b-11d4-a716-446655440000"); + + linker.add(knownId, Set.of(artifactOne.getId())); + + try { + /* ACT */ + linker.replace(knownId, Set.of(artifactTwo.getId(), unknownUuid)); + } catch (ResourceNotFoundException exception) { + /* ASSERT */ + final var entities = linker.get(knownId, Pageable.unpaged()).toList(); + assertTrue(entities.contains(artifactOne)); + assertEquals(1, entities.size()); + } + } + + @Test + public void replace_addKnownEntities_removeRelation() { + /* ARRANGE */ + final var knownId = representation.getId(); + + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ACT */ + linker.replace(knownId, Set.of(artifactTwo.getId(), artifactThree.getId())); + + /* ASSERT */ + final var elements = linker.get(knownId, Pageable.unpaged()).toList(); + assertTrue(elements.contains(artifactTwo)); + assertTrue(elements.contains(artifactThree)); + assertFalse(elements.contains(artifactOne)); + } + + @Test + public void replace_validInput_isPersisted() { + /* ARRANGE */ + final var knownId = representation.getId(); + + linker.add(knownId, Set.of(artifactOne.getId())); + + Mockito.verify(representationService, Mockito.times(1)).persist(Mockito.eq(representation)); + + /* ACT */ + linker.replace(knownId, Set.of(artifactTwo.getId(), artifactThree.getId())); + + /* ASSERT */ + Mockito.verify(representationService, Mockito.atLeast(2)) + .persist(Mockito.eq(representation)); + } + + @Test + public void replace_knownIdSetWithNullAndValidEntities_throwsNullPointerException() { + /* ARRANGE */ + final var knownId = representation.getId(); + linker.add(knownId, Set.of(artifactOne.getId())); + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, + () -> linker.replace(knownId, Set.of(artifactOne.getId(), null))); + } + + /************************************************************************** + * getInternal + *************************************************************************/ + + @Test + public void getInternal_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> linker.getInternal(null)); + } + + @Test + public void getInternal_Valid_returnArtifacts() { + /* ARRANGE */ + representation.getArtifacts().add(artifactOne); + + /* ACT */ + final var artifacts = linker.getInternal(representation); + + /* ASSERT */ + final var expected = List.of(artifactOne); + assertEquals(expected, artifacts); + } + + /************************************************************************** + * Utilities + *************************************************************************/ + + @SneakyThrows + private Representation getRepresentation() { + final var constructor = Representation.class.getConstructor(); + constructor.setAccessible(true); + + final var representation = constructor.newInstance(); + + final var titleField = representation.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(representation, "Hello"); + + final var mediaTypeField = representation.getClass().getDeclaredField("mediaType"); + mediaTypeField.setAccessible(true); + mediaTypeField.set(representation, "application/json"); + + final var languageField = representation.getClass().getDeclaredField("language"); + languageField.setAccessible(true); + languageField.set(representation, "en"); + + final var artifactsField = representation.getClass().getDeclaredField("artifacts"); + artifactsField.setAccessible(true); + artifactsField.set(representation, new ArrayList()); + + final var idField = representation.getClass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(representation, UUID.fromString("a1ed9763-e8c4-441b-bd94-d06996fced9e")); + + final var creationDateField = + representation.getClass().getSuperclass().getDeclaredField("creationDate"); + creationDateField.setAccessible(true); + creationDateField.set(representation, ZonedDateTime.parse("2021-02-14T12:13:14+01:00")); + + final var modificationDateField = + representation.getClass().getSuperclass().getDeclaredField("modificationDate"); + modificationDateField.setAccessible(true); + modificationDateField.set(representation, ZonedDateTime.parse("2021-02-14T12:13:14+01:00")); + + final var additionalField = + representation.getClass().getSuperclass().getDeclaredField("additional"); + additionalField.setAccessible(true); + additionalField.set(representation, new HashMap<>()); + + return representation; + } + + @SneakyThrows + private ArtifactImpl getArtifactOne() { + final var constructor = ArtifactImpl.class.getConstructor(); + constructor.setAccessible(true); + + final var artifact = constructor.newInstance(); + + final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(artifact, "ArtifactOne"); + + final var idField = + artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.fromString("554ed409-03e9-4b41-a45a-4b7a8c0aa499")); + + return artifact; + } + + @SneakyThrows + private ArtifactImpl getArtifactTwo() { + final var constructor = ArtifactImpl.class.getConstructor(); + constructor.setAccessible(true); + + final var artifact = constructor.newInstance(); + + final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(artifact, "ArtifactTwo"); + + final var idField = + artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.fromString("1d853fc2-91a8-4a01-9e59-dfb742eee849")); + + return artifact; + } + + @SneakyThrows + private ArtifactImpl getArtifactThree() { + final var constructor = ArtifactImpl.class.getConstructor(); + constructor.setAccessible(true); + + final var artifact = constructor.newInstance(); + + final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(artifact, "ArtifactThree"); + + final var idField = + artifact.getClass().getSuperclass().getSuperclass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(artifact, UUID.fromString("afb43170-b8d4-4872-b923-3490de99a53b")); + + return artifact; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/resources/TemplateBuilderTest.java b/src/test/java/io/dataspaceconnector/services/resources/TemplateBuilderTest.java new file mode 100644 index 000000000..191fd77fa --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/resources/TemplateBuilderTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.resources; + +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactImpl; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.templates.ArtifactTemplate; +import io.dataspaceconnector.model.templates.ResourceTemplate; +import io.dataspaceconnector.model.templates.RuleTemplate; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +class TemplateBuilderTest { + + @MockBean + private ArtifactService artifactService; + + @MockBean + private AbstractResourceRepresentationLinker offeredResourceRepresentationLinker; + + @MockBean + private AbstractResourceRepresentationLinker requestedResourceRepresentationLinker; + + @MockBean + private OfferedResourceContractLinker offeredResourceContractLinker; + + @MockBean + private RequestedResourceContractLinker requestedResourceContractLinker; + + @Autowired + TemplateBuilder builder; + + @Test + public void build_ResourceTemplateNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> builder.build(( ResourceTemplate ) null)); + } + + @Test + public void build_ResourceTemplateOnlyDesc_returnOnlyResource() { + /* ARRANGE */ + final var desc = new OfferedResourceDesc(); + final var template = new ResourceTemplate<>(desc); + + /* ACT */ + final var result = builder.build(template); + + /* ASSERT */ + assertNotNull(result); + Mockito.verify(offeredResourceRepresentationLinker, Mockito.atLeastOnce()).add(Mockito.any(), Mockito.any()); + Mockito.verify(offeredResourceContractLinker, Mockito.atLeastOnce()).add(Mockito.any(), Mockito.any()); + } + + /** + * ArtifactTemplate. + */ + + @Test + public void build_ArtifactTemplateNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> builder.build((ArtifactTemplate) null)); + } + + @Test + public void build_ArtifactTemplateValid_returnNewArtifact() { + /* ARRANGE */ + final var desc = new ArtifactDesc(); + desc.setTitle("Some title"); + final var template = new ArtifactTemplate(desc); + + final var artifact = getArtifact(desc); + + Mockito.when(artifactService.create(desc)).thenReturn(artifact); + + /* ACT */ + final var result = builder.build(template); + + /* ASSERT */ + assertEquals("Some title", result.getTitle()); + } + + /** + * RuleTemplate. + */ + + @Test + public void build_RuleTemplateNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> builder.build((RuleTemplate) null)); + } + + @Test + public void build_RuleTemplateValid_returnNewRule() { + /* ARRANGE */ + final var desc = new ContractRuleDesc(); + desc.setTitle("Some title"); + final var template = new RuleTemplate(desc); + + /* ACT */ + final var result = builder.build(template); + + /* ASSERT */ + assertEquals("Some title", result.getTitle()); + } + + /** + * Utilities + */ + + @SneakyThrows + private Artifact getArtifact(ArtifactDesc desc) { + final var artifactConstructor = ArtifactImpl.class.getConstructor(); + final var artifact = artifactConstructor.newInstance(); + + final var titleField = artifact.getClass().getSuperclass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(artifact, desc.getTitle()); + + return artifact; + } +} diff --git a/src/test/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifierTest.java b/src/test/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifierTest.java new file mode 100644 index 000000000..1951a0760 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/usagecontrol/AllowAccessVerifierTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import io.dataspaceconnector.model.ArtifactImpl; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class AllowAccessVerifierTest { + @Test + public void verify_null_returnAllowed() { + /* ARRANGE */ + final var verifier = new AllowAccessVerifier(); + + /* ACT */ + final var result = verifier.verify(null); + + /* ASSERT */ + assertEquals(VerificationResult.ALLOWED, result); + } + + + @Test + public void verify_any_returnAllowed() { + /* ARRANGE */ + final var verifier = new AllowAccessVerifier(); + + final var artifact = new ArtifactImpl(); + + /* ACT */ + final var result = verifier.verify(artifact); + + /* ASSERT */ + assertEquals(VerificationResult.ALLOWED, result); + } +} diff --git a/src/test/java/io/dataspaceconnector/services/usagecontrol/PolicyPatternTest.java b/src/test/java/io/dataspaceconnector/services/usagecontrol/PolicyPatternTest.java new file mode 100644 index 000000000..88a59a35a --- /dev/null +++ b/src/test/java/io/dataspaceconnector/services/usagecontrol/PolicyPatternTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.services.usagecontrol; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PolicyPatternTest { + + @Test + public void toString_nothing_correctMsg() { + /* ARRANGE */ + final var input = PolicyPattern.PROVIDE_ACCESS; + + /* ACT */ + final var msg = input.toString(); + + /* ASSERT */ + assertEquals("PROVIDE_ACCESS", msg); + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/ControllerUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/ControllerUtilsTest.java new file mode 100644 index 000000000..2830c997e --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/ControllerUtilsTest.java @@ -0,0 +1,241 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {ControllerUtils.class}) +class ControllerUtilsTest { + + @Test + public void respondIdsMessageFailed_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Ids message handling failed. " + + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondIdsMessageFailed(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondReceivedInvalidResponse_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Failed to read the ids response " + + "message.", HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondReceivedInvalidResponse(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondConfigurationUpdateError_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Failed to update configuration.", + HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondConfigurationUpdateError(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondDeserializationError_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Failed to update.", + HttpStatus.BAD_REQUEST); + + /* ACT */ + final var response = ControllerUtils.respondDeserializationError(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondConfigurationNotFound_null_returnValidResponseEntity() { + /* ARRANGE */ + final var expectedResponse = new ResponseEntity<>("No configuration found.", + HttpStatus.NOT_FOUND); + + /* ACT */ + final var response = ControllerUtils.respondConfigurationNotFound(); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondDeserializationError_validUri_returnValidResponseEntity() { + /* ARRANGE */ + final var resourceId = URI.create("https://requestedResource"); + final var expectedResponse = new ResponseEntity<>(String.format("Resource %s not found.", + resourceId), HttpStatus.NOT_FOUND); + + /* ACT */ + final var response = ControllerUtils.respondResourceNotFound(resourceId); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondResourceCouldNotBeLoaded_validUri_returnValidResponseEntity() { + /* ARRANGE */ + final var resourceId = URI.create("https://requestedResource"); + final var expectedResponse = new ResponseEntity<>(String.format("Could not load resource " + + "%s.", + resourceId), HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondResourceCouldNotBeLoaded(resourceId); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondPatternNotIdentified_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Could not identify pattern.", + HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondPatternNotIdentified(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondInvalidInput_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Invalid input. " + + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondInvalidInput(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondFailedToBuildContractRequest_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Failed to build contract request.", + HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondFailedToBuildContractRequest(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondConnectorNotLoaded_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Connector could not be loaded.", + HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondConnectorNotLoaded(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondGlobalException_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Something else went wrong.", + HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondGlobalException(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondFailedToStoreEntity_validException_returnValidResponseEntity() { + /* ARRANGE */ + final var exception = new Exception("Some exception."); + final var expectedResponse = new ResponseEntity<>("Failed to store entity. " + + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + + /* ACT */ + final var response = ControllerUtils.respondFailedToStoreEntity(exception); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + + @Test + public void respondWithMessageContent_validMap_returnValidResponseEntity() { + /* ARRANGE */ + final var obj = new Object(); + final var map = Map.of("header", obj); + final var expectedResponse = new ResponseEntity<>(map, HttpStatus.EXPECTATION_FAILED); + + /* ACT */ + final var response = ControllerUtils.respondWithMessageContent(map); + + /* ARRANGE */ + assertEquals(ResponseEntity.class, response.getClass()); + assertEquals(expectedResponse, response); + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/ErrorMessagesTest.java b/src/test/java/io/dataspaceconnector/utils/ErrorMessagesTest.java new file mode 100644 index 000000000..0ba693e9f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/ErrorMessagesTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ErrorMessagesTest { + + @Test + void toString_nothing_correctMsg() { + /* ARRANGE */ + final var input = ErrorMessages.DESC_NULL; + + /* ACT */ + final var msg = input.toString(); + + /* ASSERT */ + assertEquals("The description parameter may not be null.", msg); + } +} diff --git a/src/test/java/io/dataspaceconnector/utils/IdsUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/IdsUtilsTest.java new file mode 100644 index 000000000..a5596604c --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/IdsUtilsTest.java @@ -0,0 +1,454 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import java.math.BigInteger; +import java.net.URI; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.ArtifactBuilder; +import de.fraunhofer.iais.eis.BaseConnector; +import de.fraunhofer.iais.eis.BaseConnectorBuilder; +import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +import de.fraunhofer.iais.eis.ContractAgreement; +import de.fraunhofer.iais.eis.ContractAgreementBuilder; +import de.fraunhofer.iais.eis.ContractOffer; +import de.fraunhofer.iais.eis.ContractOfferBuilder; +import de.fraunhofer.iais.eis.ContractRequest; +import de.fraunhofer.iais.eis.ContractRequestBuilder; +import de.fraunhofer.iais.eis.Duty; +import de.fraunhofer.iais.eis.DutyBuilder; +import de.fraunhofer.iais.eis.IANAMediaTypeBuilder; +import de.fraunhofer.iais.eis.KeyType; +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.PermissionBuilder; +import de.fraunhofer.iais.eis.Prohibition; +import de.fraunhofer.iais.eis.PublicKeyBuilder; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.SecurityProfile; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.RdfBuilderException; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class IdsUtilsTest { + + ZonedDateTime date = ZonedDateTime.of(LocalDateTime.ofEpochSecond(1616772571804L, 0, ZoneOffset.UTC), ZoneId.of("Z")); + + @Test + public void toRdf_inputNull_throwRdfBuilderException() { + /* ARRANGE */ + Rule rule = null; + + /* ACT && ASSERT */ + assertThrows(RdfBuilderException.class, () -> IdsUtils.toRdf(rule)); + } + + @Test + public void toRdf_baseConnector_returnBaseConnectorInRdf() { + /* ACT && ASSERT */ + assertEquals(getBaseConnector().toRdf(), IdsUtils.toRdf(getBaseConnector())); + } + + @Test + public void toRdf_resource_returnResourceInRdf() { + /* ACT && ASSERT */ + assertEquals(getResource().toRdf(), IdsUtils.toRdf(getResource())); + } + + @Test + public void toRdf_artifact_returnArtifactInRdf() { + /* ACT && ASSERT */ + assertEquals(getArtifact().toRdf(), IdsUtils.toRdf(getArtifact())); + } + + @Test + public void toRdf_representation_returnRepresentationInRdf() { + /* ACT && ASSERT */ + assertEquals(getRepresentation().toRdf(), IdsUtils.toRdf(getRepresentation())); + } + + @Test + public void toRdf_contractRequest_returnContractRequestInRdf() { + /* ACT && ASSERT */ + assertEquals(getContractRequest().toRdf(), IdsUtils.toRdf(getContractRequest())); + } + + @Test + public void toRdf_contractAgreement_returnContractAgreementInRdf() { + /* ACT && ASSERT */ + assertEquals(getContractAgreement().toRdf(), IdsUtils.toRdf(getContractAgreement())); + } + + @Test + public void toRdf_rule_returnRuleInRdf() { + /* ACT && ASSERT */ + assertEquals(getRule().toRdf(), IdsUtils.toRdf(getRule())); + } + + @Test + public void getKeywordsAsTypedLiteral_keywordsNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> IdsUtils.getKeywordsAsTypedLiteral(null, "en")); + } + + @Test + public void getKeywordsAsTypedLiteral_languageNull_createTypedLiteralWithLanguageNull() { + /* ARRANGE */ + final var keyword = "keyword"; + final var keywords = Collections.singletonList(keyword); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsTypedLiteral(keywords, null); + + /* ASSERT */ + assertEquals(1, result.size()); + assertEquals(keyword, result.get(0).getValue()); + assertNull(result.get(0).getLanguage()); + } + + @Test + public void getKeywordsAsTypedLiteral_keywordsEmpty_returnEmptyList() { + /* ARRANGE */ + final var keywords = new ArrayList(); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsTypedLiteral(keywords, "en"); + + /* ASSERT */ + assertTrue(result.isEmpty()); + } + + @Test + public void getKeywordsAsTypedLiteral_oneKeyword_returnList() { + /* ARRANGE */ + final var language = "en"; + final var keyword = "keyword"; + final var keywords = Collections.singletonList(keyword); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsTypedLiteral(keywords, language); + + /* ASSERT */ + assertEquals(1, result.size()); + assertEquals(keyword, result.get(0).getValue()); + assertEquals(language, result.get(0).getLanguage()); + } + + @Test + public void getKeywordsAsTypedLiteral_twoKeywords_returnList() { + /* ARRANGE */ + final var language = "en"; + final var keyword1 = "keyword1"; + final var keyword2 = "keyword2"; + final var keywords = Arrays.asList(keyword1, keyword2); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsTypedLiteral(keywords, language); + + /* ASSERT */ + assertEquals(2, result.size()); + assertEquals(language, result.get(0).getLanguage()); + assertEquals(language, result.get(1).getLanguage()); + assertTrue(typedLiteralListContainsValues(result, keywords)); + } + + @Test + public void getLanguage_languageNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> IdsUtils.getLanguage(null)); + } + + @Test + public void getLanguage_languageEmpty_returnLanguageEnglish() { + /* ACT && ASSERT */ + assertEquals(Language.EN, IdsUtils.getLanguage("")); + } + + @Test + public void getLanguage_invalidLanguageString_returnLanguageEnglish() { + /* ACT && ASSERT */ + assertEquals(Language.EN, IdsUtils.getLanguage("noValidLanguage")); + } + + @Test + public void getLanguage_languageValueDe_returnLanguageGerman() { + /* ACT && ASSERT */ + assertEquals(Language.DE, IdsUtils.getLanguage("de")); + } + + @Test + public void getLanguage_languageValueEn_returnLanguageEnglish() { + /* ACT && ASSERT */ + assertEquals(Language.EN, IdsUtils.getLanguage("en")); + } + + @Test + public void getLanguage_languageValueDE_returnLanguageGerman() { + /* ACT && ASSERT */ + assertEquals(Language.DE, IdsUtils.getLanguage("DE")); + } + + @Test + public void getKeywordsAsString_keywordsNull_returnEmptyList() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = IdsUtils.getKeywordsAsString(null); + + /* ASSERT */ + assertEquals(0, result.size()); + } + + @Test + public void getKeywordsAsString_keywordsEmpty_returnsEmptyList() { + /* ACT */ + final var result = IdsUtils.getKeywordsAsString(new ArrayList<>()); + + /* ASSERT */ + assertTrue(result.isEmpty()); + } + + @Test + public void getKeywordsAsString_oneKeyword_returnsKeywordValueInList() { + /* ARRANGE */ + final var keywordAsString = "keyword"; + final var keyword = new TypedLiteral(keywordAsString, "en"); + final var keywords = new ArrayList<>(Collections.singletonList(keyword)); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsString(keywords); + + /* ASSERT */ + assertEquals(1, result.size()); + assertTrue(result.contains(keywordAsString)); + } + + @Test + public void getKeywordsAsString_twoKeywords_returnsKeywordValuesInList() { + /* ARRANGE */ + final var keywordAsString1 = "keyword"; + final var keyword1 = new TypedLiteral(keywordAsString1, "en"); + final var keywordAsString2 = "keyword"; + final var keyword2 = new TypedLiteral(keywordAsString2, "en"); + final var keywords = new ArrayList<>(Arrays.asList(keyword1, keyword2)); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsString(keywords); + + /* ASSERT */ + assertEquals(2, result.size()); + assertTrue(result.containsAll(Arrays.asList(keywordAsString1, keywordAsString2))); + } + + @Test + public void getKeywordsAsString_keywordValueNull_returnsNullInList() { + /* ARRANGE */ + final var keyword = new TypedLiteral(null, "en"); + final var keywords = new ArrayList<>(Collections.singletonList(keyword)); + + /* ACT */ + final var result = IdsUtils.getKeywordsAsString(keywords); + + /* ASSERT */ + assertEquals(1, result.size()); + assertTrue(result.contains(null)); + } + + @Test + public void getGregorianOf_inputNull_throwNullPointerException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> IdsUtils.getGregorianOf((ZonedDateTime) null)); + } + + @Test + public void getGregorianOf_validDate_returnDateAsXMLGregorianCalendar() { + /* ACT */ + final var result = IdsUtils.getGregorianOf(date); + + /* ASSERT */ + final var calendar = GregorianCalendar.from(date); + + assertEquals(calendar.get(Calendar.YEAR), result.getYear()); + assertEquals(calendar.get(Calendar.MONTH) + 1, result.getMonth()); + assertEquals(calendar.get(Calendar.DAY_OF_MONTH), result.getDay()); + assertEquals(calendar.get(Calendar.HOUR_OF_DAY), result.getHour()); + assertEquals(calendar.get(Calendar.MINUTE), result.getMinute()); + assertEquals(calendar.get(Calendar.SECOND), result.getSecond()); + + final var resultMilliseconds = (int) (result.getFractionalSecond().doubleValue() * 1000); + assertEquals(calendar.get(Calendar.MILLISECOND), resultMilliseconds); + } + + @Test + public void getGregorianOf_validDate_returnValidISO8601Time() { + /* ACT */ + final var result = IdsUtils.getGregorianOf(date).toString(); + + /* ASSERT */ + assertEquals("53203-06-26T12:10:04.000Z", result); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private boolean typedLiteralListContainsValues(final List list, List values) { + final var listValues = new ArrayList<>(); + for (var listElement: list) { + listValues.add(listElement.getValue()); + } + return listValues.containsAll(values); + } + + private BaseConnector getBaseConnector() { + return new BaseConnectorBuilder(URI.create("https://w3id.org/idsa/autogen/baseConnector/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._maintainer_(URI.create("https://example.com")) + ._curator_(URI.create("https://example.com")) + ._securityProfile_(SecurityProfile.BASE_SECURITY_PROFILE) + ._outboundModelVersion_("4.0.0") + ._inboundModelVersion_(Util.asList("4.0.0")) + ._title_(Util.asList(new TypedLiteral("Dataspace Connector"))) + ._description_(Util.asList(new TypedLiteral( + "Test Connector"))) + ._version_("v3.0.0") + ._publicKey_(new PublicKeyBuilder(URI.create("https://w3id.org/idsa/autogen/publicKey/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._keyType_(KeyType.RSA) + ._keyValue_("public key".getBytes()) + .build() + ) + ._hasDefaultEndpoint_(new ConnectorEndpointBuilder(URI.create("https://w3id.org/idsa/autogen/connectorEndpoint/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._accessURL_(URI.create("/api/ids/data")) + .build()) + .build(); + } + + private Resource getResource() { + return new ResourceBuilder(URI.create("https://w3id.org/idsa/autogen/resource/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._contractOffer_(Util.asList(getContractOffer())) + ._created_(getDateAsXMLGregorianCalendar()) + ._description_(Util.asList(new TypedLiteral("description", "EN"))) + ._language_(Util.asList(Language.EN)) + ._modified_(getDateAsXMLGregorianCalendar()) + ._publisher_(URI.create("http://publisher.com")) + ._representation_(Util.asList(getRepresentation())) + ._resourceEndpoint_(Util.asList(new ConnectorEndpointBuilder(URI.create("https://w3id.org/idsa/autogen/connectorEndpoint/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._accessURL_(URI.create("http://connector-endpoint.com")) + .build())) + ._sovereign_(URI.create("http://sovereign.com")) + ._standardLicense_(URI.create("http://license.com")) + ._title_(Util.asList(new TypedLiteral("title", "EN"))) + ._version_("1.0") + .build(); + } + + private Artifact getArtifact() { + return new ArtifactBuilder(URI.create("https://w3id.org/idsa/autogen/artifact/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._byteSize_(BigInteger.ONE) + ._creationDate_(getDateAsXMLGregorianCalendar()) + ._fileName_("file name") + .build(); + } + + private Representation getRepresentation() { + return new RepresentationBuilder(URI.create("https://w3id.org/idsa/autogen/representation/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._created_(getDateAsXMLGregorianCalendar()) + ._instance_(Util.asList(getArtifact())) + ._language_(Language.EN) + ._mediaType_(new IANAMediaTypeBuilder(URI.create("https://w3id.org/idsa/autogen/mediaType/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._filenameExtension_("json") + .build()) + ._modified_(getDateAsXMLGregorianCalendar()) + ._representationStandard_(URI.create("http://standard.com")) + .build(); + } + + private ContractOffer getContractOffer() { + return new ContractOfferBuilder(URI.create("https://w3id.org/idsa/autogen/contractOffer/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + .build(); + } + + private ContractRequest getContractRequest() { + final var permissions = new ArrayList(); + final var prohibitions = new ArrayList(); + final var obligations = new ArrayList(); + + permissions.add((Permission) getRule()); + + return new ContractRequestBuilder(URI.create("https://w3id.org/idsa/autogen/contractRequest/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._consumer_(URI.create("https://w3id.org/idsa/autogen/baseConnector/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._contractDate_(getDateAsXMLGregorianCalendar()) + ._contractStart_(getDateAsXMLGregorianCalendar()) + ._obligation_(obligations) + ._permission_(permissions) + ._prohibition_(prohibitions) + .build(); + } + + private ContractAgreement getContractAgreement() { + return new ContractAgreementBuilder(URI.create("https://w3id.org/idsa/autogen/contractAgreement/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._provider_(URI.create("http://provider.com")) + ._consumer_(URI.create("http://consumer.com")) + ._permission_(Util.asList((Permission) getRule())) + ._contractDate_(getDateAsXMLGregorianCalendar()) + ._contractStart_(getDateAsXMLGregorianCalendar()) + ._contractEnd_(getDateAsXMLGregorianCalendar()) + .build(); + } + + private Rule getRule() { + return new PermissionBuilder(URI.create("https://w3id.org/idsa/autogen/permission/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-logging"))) + ._action_(Util.asList(Action.USE)) + ._postDuty_(Util.asList(new DutyBuilder(URI.create("https://w3id.org/idsa/autogen/duty/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._action_(Util.asList(Action.LOG)) + .build())) + .build(); + } + + @SneakyThrows + private XMLGregorianCalendar getDateAsXMLGregorianCalendar() { + GregorianCalendar calendar = GregorianCalendar.from(date); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/MappingUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/MappingUtilsTest.java new file mode 100644 index 000000000..029840cf0 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/MappingUtilsTest.java @@ -0,0 +1,410 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.Artifact; +import de.fraunhofer.iais.eis.ArtifactBuilder; +import de.fraunhofer.iais.eis.ConnectorEndpointBuilder; +import de.fraunhofer.iais.eis.ContentType; +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.ContractAgreementBuilder; +import de.fraunhofer.iais.eis.ContractOffer; +import de.fraunhofer.iais.eis.ContractOfferBuilder; +import de.fraunhofer.iais.eis.DutyBuilder; +import de.fraunhofer.iais.eis.Frequency; +import de.fraunhofer.iais.eis.GeoPointBuilder; +import de.fraunhofer.iais.eis.IANAMediaTypeBuilder; +import de.fraunhofer.iais.eis.Language; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.PermissionBuilder; +import de.fraunhofer.iais.eis.Representation; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.TemporalEntityBuilder; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.GregorianCalendar; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MappingUtilsTest { + + private final ZonedDateTime date = + ZonedDateTime.ofInstant(Instant.ofEpochMilli(1616772571804L), ZoneOffset.UTC); + + @Test + public void fromIdsResource_inputNull_throwIllegalArgumentExceptionException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MappingUtils.fromIdsResource(null)); + } + + @Test + public void fromIdsResource_validInput_returnResourceTemplate() { + /* ARRANGE */ + final var resource = getResource(); + resource.setProperty("test", "test"); + + /* ACT */ + final var result = MappingUtils.fromIdsResource(resource); + + /* ASSERT */ + assertEquals(resource.getId(), result.getDesc().getRemoteId()); + assertEquals(resource.getKeyword().get(0).getValue(), result.getDesc().getKeywords().get(0)); + assertEquals(resource.getDescription().toString(), result.getDesc().getDescription()); + assertEquals(resource.getPublisher(), result.getDesc().getPublisher()); + assertEquals(resource.getStandardLicense(), result.getDesc().getLicence()); + assertEquals(resource.getLanguage().toString(), result.getDesc().getLanguage()); + assertEquals(resource.getTitle().toString(), result.getDesc().getTitle()); + assertEquals(resource.getSovereign(), result.getDesc().getSovereign()); + assertEquals(resource.getResourceEndpoint().get(0).getEndpointDocumentation().get(0), result.getDesc().getEndpointDocumentation()); + + final var additional = result.getDesc().getAdditional(); + assertEquals(resource.getAccrualPeriodicity().toRdf(), additional.get("ids:accrualPeriodicity")); + assertEquals(resource.getContentPart().toString(), additional.get("ids:contentPart")); + assertEquals(resource.getContentStandard().toString(), additional.get("ids:contentStandard")); + assertEquals(resource.getContentType().toRdf(), additional.get("ids:contentType")); + assertEquals(resource.getCreated().toXMLFormat(), additional.get("ids:created")); + assertEquals(resource.getCustomLicense().toString(), additional.get("ids:customLicense")); + assertEquals(resource.getDefaultRepresentation().toString(), additional.get("ids:defaultRepresentation")); + assertEquals(resource.getModified().toXMLFormat(), additional.get("ids:modified")); + assertEquals(resource.getResourceEndpoint().toString(), additional.get("ids:resourceEndpoint")); + assertEquals(resource.getResourcePart().toString(), additional.get("ids:resourcePart")); + assertEquals(resource.getSample().toString(), additional.get("ids:sample")); + assertEquals(resource.getShapesGraph().toString(), additional.get("ids:shapesGraph")); + assertEquals(resource.getSpatialCoverage().toString(), additional.get("ids:spatialCoverage")); + assertEquals(resource.getTemporalCoverage().toString(), additional.get("ids:temporalCoverage")); + assertEquals(resource.getTemporalResolution().toString(), additional.get("ids:temporalResolution")); + assertEquals(resource.getTheme().toString(), additional.get("ids:theme")); + assertEquals(resource.getVariant().toString(), additional.get("ids:variant")); + assertEquals(resource.getVersion(), additional.get("ids:version")); + assertEquals("test", additional.get("test")); + } + + @Test + public void fromIdsRepresentation_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MappingUtils.fromIdsRepresentation(null)); + } + + @Test + public void fromIdsRepresentation_validInput_returnRepresentationTemplate() { + /* ARRANGE */ + final var representation = getRepresentation(); + representation.setProperty("test", "test"); + + /* ACT */ + final var result = MappingUtils.fromIdsRepresentation(representation); + + /* ASSERT */ + assertEquals(representation.getId(), result.getDesc().getRemoteId()); + assertEquals(representation.getMediaType().getFilenameExtension(), result.getDesc().getMediaType()); + assertEquals(representation.getLanguage().toString(), result.getDesc().getLanguage()); + assertEquals(representation.getRepresentationStandard().toString(), result.getDesc().getStandard()); + + final var additional = result.getDesc().getAdditional(); + assertEquals(representation.getCreated().toXMLFormat(), additional.get("ids:created")); + assertEquals(representation.getModified().toXMLFormat(), additional.get("ids:modified")); + assertEquals(representation.getShapesGraph().toString(), additional.get("ids:shapesGraph")); + } + + @Test + public void fromIdsArtifact_artifactNull_throwIllegalArgumentException() { + /* ARRANGE */ + final var remoteAddress = URI.create("https://someURL"); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MappingUtils.fromIdsArtifact(null, true, remoteAddress)); + } + + @Test + public void fromIdsArtifact_validInput_returnArtifactTemplate() { + /* ARRANGE */ + final var artifact = getArtifact(); + artifact.setProperty("test", "test"); + final var download = true; + final var remoteAddress = URI.create("https://someURL"); + + /* ACT */ + final var result = MappingUtils.fromIdsArtifact(artifact, download, remoteAddress); + + /* ASSERT */ + assertEquals(artifact.getId(), result.getDesc().getRemoteId()); + assertEquals(artifact.getFileName(), result.getDesc().getTitle()); + assertEquals(download, result.getDesc().isAutomatedDownload()); + + final var additional = result.getDesc().getAdditional(); + assertEquals(artifact.getByteSize().toString(), additional.get("ids:byteSize")); + assertEquals(artifact.getCheckSum(), additional.get("ids:checkSum")); + assertEquals(artifact.getCreationDate().toXMLFormat(), additional.get("ids:creationDate")); + assertEquals(artifact.getDuration().toString(), additional.get("ids:duration")); + assertEquals("test", additional.get("test")); + } + + @Test + public void fromIdsContract_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MappingUtils.fromIdsContract(null)); + } + + @SneakyThrows + @Test + public void fromIdsContract_validInput_returnContractTemplate() { + /* ARRANGE */ + final var contract = getContract(); + contract.setProperty("test", "test"); + + /* ACT */ + final var result = MappingUtils.fromIdsContract(contract); + + /* ASSERT */ + assertEquals(contract.getId(), result.getDesc().getRemoteId()); + assertEquals(contract.getProvider(), result.getDesc().getProvider()); + assertEquals(contract.getConsumer(), result.getDesc().getConsumer()); + assertTrue(date.isEqual(result.getDesc().getStart())); + assertTrue(date.isEqual(result.getDesc().getEnd())); + + final var additional = result.getDesc().getAdditional(); + assertEquals("test", additional.get("test")); + } + + @Test + public void fromIdsContract_contractEndNull_throwNullPointerException() { + /* ARRANGE */ + final var contract = getContractWithEndDateNull(); + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> MappingUtils.fromIdsContract(contract)); + } + + @Test + public void fromIdsRule_validInput_returnRuleTemplate() { + /* ARRANGE */ + final var rule = getRule(); + + /* ACT */ + final var result = MappingUtils.fromIdsRule(rule); + + /* ASSERT */ + assertEquals(rule.getId(), result.getDesc().getRemoteId()); + assertEquals(rule.getTitle().toString(), result.getDesc().getTitle()); + assertEquals(rule.toRdf(), result.getDesc().getValue()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + @SneakyThrows + private Resource getResource() { + return new ResourceBuilder(URI.create("https://w3id.org/idsa/autogen/resource/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._contractOffer_(Util.asList(getContractOffer())) + ._created_(getDateAsXMLGregorianCalendar()) + ._description_(Util.asList(new TypedLiteral("description", "EN"))) + ._language_(Util.asList(Language.EN)) + ._modified_(getDateAsXMLGregorianCalendar()) + ._publisher_(URI.create("http://publisher.com")) + ._representation_(Util.asList(getRepresentation())) + ._resourceEndpoint_(Util.asList(new ConnectorEndpointBuilder(URI.create("https://w3id.org/idsa/autogen/connectorEndpoint/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._accessURL_(URI.create("http://connector-endpoint.com")) + ._endpointDocumentation_(Util.asList(URI.create("http://connector-endpoint-docs.com"))) + .build())) + ._sovereign_(URI.create("http://sovereign.com")) + ._standardLicense_(URI.create("http://license.com")) + ._title_(Util.asList(new TypedLiteral("title", "EN"))) + ._version_("1.0") + ._accrualPeriodicity_(Frequency.DAILY) + ._contentPart_(Util.asList(new ResourceBuilder().build())) + ._contentStandard_(URI.create("http://standard.com")) + ._contentType_(ContentType.SCHEMA_DEFINITION) + ._customLicense_(URI.create("http://license.com")) + ._defaultRepresentation_(Util.asList(getRepresentation())) + ._resourcePart_(Util.asList(new ResourceBuilder().build())) + ._sample_(Util.asList(new ResourceBuilder().build())) + ._shapesGraph_(URI.create("http://shapes-graph.com")) + ._sovereign_(URI.create("http://sovereign.com")) + ._spatialCoverage_(Util.asList(new GeoPointBuilder() + ._latitude_(12.3f) + ._longitude_(45.6f) + .build())) + ._standardLicense_(URI.create("http://license.com")) + ._temporalCoverage_(Util.asList(new TemporalEntityBuilder() + ._hasDuration_(DatatypeFactory.newInstance().newDuration("P3M")) + .build())) + ._temporalResolution_(Frequency.DAILY) + ._theme_(Util.asList(URI.create("http://theme.com"))) + ._variant_(new ResourceBuilder().build()) + ._keyword_(Util.asList(new TypedLiteral("keyword", "EN"))) + .build(); + } + + @SneakyThrows + private Resource getResourceWithKeywordsNull() { + return new ResourceBuilder(URI.create("https://w3id.org/idsa/autogen/resource/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._contractOffer_(Util.asList(getContractOffer())) + ._created_(getDateAsXMLGregorianCalendar()) + ._description_(Util.asList(new TypedLiteral("description", "EN"))) + ._language_(Util.asList(Language.EN)) + ._modified_(getDateAsXMLGregorianCalendar()) + ._publisher_(URI.create("http://publisher.com")) + ._representation_(Util.asList(getRepresentation())) + ._resourceEndpoint_(Util.asList(new ConnectorEndpointBuilder(URI.create("https://w3id.org/idsa/autogen/connectorEndpoint/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._accessURL_(URI.create("http://connector-endpoint.com")) + ._endpointDocumentation_(Util.asList(URI.create("http://connector-endpoint-docs.com"))) + .build())) + ._sovereign_(URI.create("http://sovereign.com")) + ._standardLicense_(URI.create("http://license.com")) + ._title_(Util.asList(new TypedLiteral("title", "EN"))) + ._version_("1.0") + ._accrualPeriodicity_(Frequency.DAILY) + ._contentPart_(Util.asList(new ResourceBuilder().build())) + ._contentStandard_(URI.create("http://standard.com")) + ._contentType_(ContentType.SCHEMA_DEFINITION) + ._customLicense_(URI.create("http://license.com")) + ._defaultRepresentation_(Util.asList(getRepresentation())) + ._resourcePart_(Util.asList(new ResourceBuilder().build())) + ._sample_(Util.asList(new ResourceBuilder().build())) + ._shapesGraph_(URI.create("http://shapes-graph.com")) + ._sovereign_(URI.create("http://sovereign.com")) + ._spatialCoverage_(Util.asList(new GeoPointBuilder() + ._latitude_(12.3f) + ._longitude_(45.6f) + .build())) + ._standardLicense_(URI.create("http://license.com")) + ._temporalCoverage_(Util.asList(new TemporalEntityBuilder() + ._hasDuration_(DatatypeFactory.newInstance().newDuration("P3M")) + .build())) + ._temporalResolution_(Frequency.DAILY) + ._theme_(Util.asList(URI.create("http://theme.com"))) + ._variant_(new ResourceBuilder().build()) + .build(); + } + + private ContractOffer getContractOffer() { + return new ContractOfferBuilder(URI.create("https://w3id.org/idsa/autogen/contractOffer/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + .build(); + } + + private Representation getRepresentation() { + return new RepresentationBuilder(URI.create("https://w3id.org/idsa/autogen/representation/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._created_(getDateAsXMLGregorianCalendar()) + ._instance_(Util.asList(getArtifact())) + ._language_(Language.EN) + ._mediaType_(new IANAMediaTypeBuilder(URI.create("https://w3id.org/idsa/autogen/mediaType/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._filenameExtension_("json") + .build()) + ._modified_(getDateAsXMLGregorianCalendar()) + ._representationStandard_(URI.create("http://standard.com")) + ._shapesGraph_(URI.create("http://shapes-graph.com")) + .build(); + } + + private Representation getRepresentationWithMediaTypeNull() { + return new RepresentationBuilder(URI.create("https://w3id.org/idsa/autogen/representation/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._created_(getDateAsXMLGregorianCalendar()) + ._instance_(Util.asList(getArtifact())) + ._language_(Language.EN) + ._modified_(getDateAsXMLGregorianCalendar()) + ._representationStandard_(URI.create("http://standard.com")) + ._shapesGraph_(URI.create("http://shapes-graph.com")) + .build(); + } + + private Artifact getArtifact() { + return new ArtifactBuilder(URI.create("https://w3id.org/idsa/autogen/artifact/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._byteSize_(BigInteger.ONE) + ._checkSum_("check sum") + ._creationDate_(getDateAsXMLGregorianCalendar()) + ._duration_(new BigDecimal("123.4")) + ._fileName_("file name") + .build(); + } + + private Artifact getArtifactWithCreationDateNull() { + return new ArtifactBuilder(URI.create("https://w3id.org/idsa/autogen/artifact/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._byteSize_(BigInteger.ONE) + ._checkSum_("check sum") + ._duration_(new BigDecimal("123.4")) + ._fileName_("file name") + .build(); + } + + private Contract getContract() { + return new ContractAgreementBuilder(URI.create("https://w3id.org/idsa/autogen/contractAgreement/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._provider_(URI.create("http://provider.com")) + ._consumer_(URI.create("http://consumer.com")) + ._permission_(Util.asList((Permission) getRule())) + ._contractDate_(getDateAsXMLGregorianCalendar()) + ._contractStart_(getDateAsXMLGregorianCalendar()) + ._contractEnd_(getDateAsXMLGregorianCalendar()) + .build(); + } + + private Contract getContractWithEndDateNull() { + return new ContractAgreementBuilder(URI.create("https://w3id.org/idsa/autogen/contractAgreement/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._provider_(URI.create("http://provider.com")) + ._consumer_(URI.create("http://consumer.com")) + ._permission_(Util.asList((Permission) getRule())) + ._contractDate_(getDateAsXMLGregorianCalendar()) + ._contractStart_(getDateAsXMLGregorianCalendar()) + .build(); + } + + private Rule getRule() { + return new PermissionBuilder(URI.create("https://w3id.org/idsa/autogen/permission/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._title_(Util.asList(new TypedLiteral("Example Usage Policy"))) + ._description_(Util.asList(new TypedLiteral("usage-logging"))) + ._action_(Util.asList(Action.USE)) + ._postDuty_(Util.asList(new DutyBuilder(URI.create("https://w3id.org/idsa/autogen/duty/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._action_(Util.asList(Action.LOG)) + .build())) + .build(); + } + + private Rule getRuleWithTitleNull() { + return new PermissionBuilder(URI.create("https://w3id.org/idsa/autogen/permission/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._description_(Util.asList(new TypedLiteral("usage-logging"))) + ._action_(Util.asList(Action.USE)) + ._postDuty_(Util.asList(new DutyBuilder(URI.create("https://w3id.org/idsa/autogen/duty/591467af-9633-4a4e-8bcf-47ba4e6679ea")) + ._action_(Util.asList(Action.LOG)) + .build())) + .build(); + } + + @SneakyThrows + private XMLGregorianCalendar getDateAsXMLGregorianCalendar() { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(Date.from(date.toInstant())); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/MessageUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/MessageUtilsTest.java new file mode 100644 index 000000000..441a93447 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/MessageUtilsTest.java @@ -0,0 +1,435 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.ArtifactRequestMessage; +import de.fraunhofer.iais.eis.ArtifactRequestMessageBuilder; +import de.fraunhofer.iais.eis.DescriptionRequestMessage; +import de.fraunhofer.iais.eis.DescriptionRequestMessageBuilder; +import de.fraunhofer.iais.eis.DynamicAttributeToken; +import de.fraunhofer.iais.eis.DynamicAttributeTokenBuilder; +import de.fraunhofer.iais.eis.RejectionMessage; +import de.fraunhofer.iais.eis.RejectionMessageBuilder; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.ResourceUpdateMessage; +import de.fraunhofer.iais.eis.ResourceUpdateMessageBuilder; +import de.fraunhofer.iais.eis.TokenFormat; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.exceptions.MessageEmptyException; +import io.dataspaceconnector.exceptions.VersionNotSupportedException; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MessageUtilsTest { + + final static URI messageId = URI.create("https://messageId"); + final static URI connectorId = URI.create("https://connectorId"); + final static URI requestedElement = URI.create("https://requestedElement"); + final static URI requestedArtifact = URI.create("https://requestedArtifact"); + final static URI affectedResource = URI.create("https://affectedResource"); + final static URI transferContract = URI.create("https://transferContract"); + final static URI correlationMessage = URI.create("https://correlationMessage"); + final static String modelVersion = "4.0.0"; + final static DynamicAttributeToken token = new DynamicAttributeTokenBuilder() + ._tokenFormat_(TokenFormat.OTHER)._tokenValue_("").build(); + + @Test + public void extractRequestedElement_messageWithRequestedElement_returnRequestedElement() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithRequestedElement(); + + /* ACT */ + final var result = MessageUtils.extractRequestedElement(message); + + /* ASSERT */ + assertEquals(requestedElement, result); + } + + @Test + public void extractRequestedElement_messageWithoutRequestedElement_returnNull() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithoutRequestedElement(); + + /* ACT */ + final var result = MessageUtils.extractRequestedElement(message); + + /* ASSERT */ + assertNull(result); + } + + @Test + public void extractRequestedElement_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractRequestedElement(null)); + } + + @Test + public void extractRequestedArtifact_messageWithRequestedArtifact_returnRequestedArtifact() { + /* ARRANGE */ + final var message = getArtifactRequestMessageWithoutTransferContract(); + + /* ACT */ + final var result = MessageUtils.extractRequestedArtifact(message); + + /* ASSERT */ + assertEquals(requestedArtifact, result); + } + + @Test + public void extractRequestedArtifact_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractRequestedElement(null)); + } + + @Test + public void extractTransferContract_messageWithTransferContract_returnTransferContract() { + /* ARRANGE */ + final var message = getArtifactRequestMessageWithTransferContract(); + + /* ACT */ + final var result = MessageUtils.extractTransferContract(message); + + /* ASSERT */ + assertEquals(transferContract, result); + } + + @Test + public void extractTransferContract_messageWithoutTransferContract_returnNull() { + /* ARRANGE */ + final var message = getArtifactRequestMessageWithoutTransferContract(); + + /* ACT */ + final var result = MessageUtils.extractTransferContract(message); + + /* ASSERT */ + assertNull(result); + } + + @Test + public void extractTransferContract_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> MessageUtils.extractTransferContract(null)); + } + + @Test + public void extractAffectedResource_messageWithAffectedResource_returnAffectedResource() { + /* ARRANGE */ + final var message = getResourceUpdateMessageWithAffectedResource(); + + /* ACT */ + final var result = MessageUtils.extractAffectedResource(message); + + /* ASSERT */ + assertEquals(affectedResource, result); + } + + @Test + public void extractAffectedResource_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractAffectedResource(null)); + } + + @Test + public void extractIssuerConnector_validMessage_returnIssuerConnector() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithRequestedElement(); + + /* ACT */ + final var result = MessageUtils.extractIssuerConnector(message); + + /* ASSERT */ + assertEquals(connectorId, result); + } + + @Test + public void extractIssuerConnector_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractIssuerConnector(null)); + } + + @Test + public void extractMessageId_validMessage_returnIssuerConnector() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithRequestedElement(); + + /* ACT */ + final var result = MessageUtils.extractMessageId(message); + + /* ASSERT */ + assertEquals(messageId, result); + } + + @Test + public void extractMessageId_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractMessageId(null)); + } + + @Test + public void extractModelVersion_validMessage_returnIssuerConnector() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithRequestedElement(); + + /* ACT */ + final var result = MessageUtils.extractModelVersion(message); + + /* ASSERT */ + assertEquals(modelVersion, result); + } + + @Test + public void extractModelVersion_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractModelVersion(null)); + } + + @Test + public void extractRejectionReason_validRejectionMessage_returnRejectionReason() { + /* ARRANGE */ + final var rejectionReason = RejectionReason.NOT_FOUND; + final var message = getRejectionMessage(rejectionReason); + + /* ACT */ + final var result = MessageUtils.extractRejectionReason(message); + + /* ASSERT */ + assertEquals(rejectionReason, result); + } + + @Test + public void extractRejectionReason_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> MessageUtils.extractRejectionReason(null)); + } + + @Test + public void checkForEmptyMessage_null_throwMessageEmptyException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(MessageEmptyException.class, () -> MessageUtils.checkForEmptyMessage(null)); + } + + @Test + public void checkForEmptyMessage_validMessage_nothing() { + /* ARRANGE */ + final var message = getDescriptionRequestMessageWithRequestedElement(); + + /* ACT & ASSERT */ + assertDoesNotThrow(() -> MessageUtils.checkForEmptyMessage(message)); + } + + @Test + public void checkForVersionSupport_validVersion_nothing() { + /* ARRANGE */ + final var inboundModelVersions = List.of("4.0.0", "4.0.1"); + + /* ACT & ASSERT */ + assertDoesNotThrow(() -> MessageUtils.checkForVersionSupport(modelVersion, inboundModelVersions)); + } + + @Test + public void checkForVersionSupport_invalidVersion_throwVersionNotSupportedException() { + /* ARRANGE */ + final var inboundModelVersions = List.of("4.0.1", "4.0.2"); + + /* ACT & ASSERT */ + assertThrows(VersionNotSupportedException.class, () -> MessageUtils.checkForVersionSupport(modelVersion, inboundModelVersions)); + } + + @Test + public void extractHeaderFromMultipartMessage_mapWithHeaderValue_returnHeaderValue() { + /* ARRANGE */ + final var headerValue = "some header values"; + final var response = new HashMap(); + response.put("header", headerValue); + response.put("payload", "some payload values"); + + /* ACT */ + final var result = MessageUtils.extractHeaderFromMultipartMessage(response); + + /* ASSERT */ + assertEquals(headerValue, result); + } + + @Test + public void extractHeaderFromMultipartMessage_mapWithoutHeaderValue_returnNull() { + /* ARRANGE */ + final var response = new HashMap(); + response.put("payload", "some payload values"); + + /* ACT */ + final var result = MessageUtils.extractHeaderFromMultipartMessage(response); + + /* ACT & ASSERT */ + assertNull(result); + } + + @Test + public void extractHeaderFromMultipartMessage_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractHeaderFromMultipartMessage(null)); + } + + @Test + public void extractPayloadFromMultipartMessage_mapWithPayloadValue_returnPayloadValue() { + /* ARRANGE */ + final var payloadValue = "some payload values"; + final var response = new HashMap(); + response.put("header", "some header values"); + response.put("payload", payloadValue); + + /* ACT */ + final var result = MessageUtils.extractPayloadFromMultipartMessage(response); + + /* ASSERT */ + assertEquals(payloadValue, result); + } + + @Test + public void extractPayloadFromMultipartMessage_mapWithoutPayloadValue_returnNull() { + /* ARRANGE */ + final var response = new HashMap(); + response.put("header", "some header values"); + + /* ACT */ + final var result = MessageUtils.extractPayloadFromMultipartMessage(response); + + /* ACT & ASSERT */ + assertNull(result); + } + + @Test + public void extractPayloadFromMultipartMessage_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> MessageUtils.extractPayloadFromMultipartMessage(null)); + } + + private DescriptionRequestMessage getDescriptionRequestMessageWithRequestedElement() { + return new DescriptionRequestMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._requestedElement_(requestedElement) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + .build(); + } + + private DescriptionRequestMessage getDescriptionRequestMessageWithoutRequestedElement() { + return new DescriptionRequestMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + .build(); + } + + private ArtifactRequestMessage getArtifactRequestMessageWithTransferContract() { + return new ArtifactRequestMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._requestedArtifact_(requestedArtifact) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + ._transferContract_(transferContract) + .build(); + } + + private ArtifactRequestMessage getArtifactRequestMessageWithoutTransferContract() { + return new ArtifactRequestMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._requestedArtifact_(requestedArtifact) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + .build(); + } + + private RejectionMessage getRejectionMessage(final RejectionReason rejectionReason) { + return new RejectionMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + ._rejectionReason_(rejectionReason) + ._correlationMessage_(correlationMessage) + .build(); + } + + private ResourceUpdateMessage getResourceUpdateMessageWithAffectedResource() { + return new ResourceUpdateMessageBuilder(messageId) + ._issued_(getGregorianNow()) + ._modelVersion_(modelVersion) + ._issuerConnector_(connectorId) + ._senderAgent_(connectorId) + ._securityToken_(token) + ._recipientConnector_(Util.asList(connectorId)) + ._affectedResource_(affectedResource) + .build(); + } +} diff --git a/src/test/java/io/dataspaceconnector/utils/PatternUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/PatternUtilsTest.java new file mode 100644 index 000000000..d0dec6e96 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/PatternUtilsTest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.BinaryOperator; +import de.fraunhofer.iais.eis.Constraint; +import de.fraunhofer.iais.eis.LeftOperand; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.Prohibition; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PatternUtilsTest { + + private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + @Test + public void buildProvideAccessRule_returnPermissionWithoutConstraintsAndPostDuties() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildProvideAccessRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertNull(rule.getConstraint()); + assertNull(((Permission) rule).getPostDuty()); + } + + @Test + public void buildProhibitAccessRule_returnProhibition() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildProhibitAccessRule(); + + /* ASSERT */ + assertTrue(Prohibition.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + } + + @Test + public void buildNTimesUsageRule_returnPermissionWithConstraint() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildNTimesUsageRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(1, rule.getConstraint().size()); + + final var constraint = (Constraint) rule.getConstraint().get(0); + assertEquals(LeftOperand.COUNT, constraint.getLeftOperand()); + + final var operator = constraint.getOperator(); + assertTrue((BinaryOperator.LT.equals(operator)) + || (BinaryOperator.LTEQ.equals(operator))); + + assertTrue(isValidInteger(constraint.getRightOperand().getValue())); + } + + @Test + public void buildDurationUsageRule_returnPermissionWithConstraint() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildDurationUsageRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(1, rule.getConstraint().size()); + + final var constraint = (Constraint) rule.getConstraint().get(0); + assertEquals(LeftOperand.ELAPSED_TIME, constraint.getLeftOperand()); + + final var operator = constraint.getOperator(); + assertTrue((BinaryOperator.SHORTER_EQ.equals(operator)) + || (BinaryOperator.SHORTER.equals(operator))); + + assertTrue(isValidDuration(constraint.getRightOperand().getValue())); + } + + @Test + public void buildIntervalUsageRule_returnPermissionWithTwoConstraints() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildIntervalUsageRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(2, rule.getConstraint().size()); + + final var constraint1 = (Constraint) rule.getConstraint().get(0); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint1.getLeftOperand()); + final var operator1 = constraint1.getOperator(); + assertTrue((BinaryOperator.BEFORE.equals(operator1)) + || (BinaryOperator.AFTER.equals(operator1))); + assertTrue(isValidDate(constraint1.getRightOperand().getValue())); + + final var constraint2 = (Constraint) rule.getConstraint().get(1); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint2.getLeftOperand()); + final var operator2 = constraint2.getOperator(); + assertTrue((BinaryOperator.BEFORE.equals(operator2)) + || (BinaryOperator.AFTER.equals(operator2))); + assertTrue(isValidDate(constraint2.getRightOperand().getValue())); + } + + @Test + public void buildUsageUntilDeletionRule_returnPermissionWithTwoConstraintsAndPostDuty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildUsageUntilDeletionRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(2, rule.getConstraint().size()); + assertEquals(1, ((Permission) rule).getPostDuty().size()); + + final var constraint1 = (Constraint) rule.getConstraint().get(0); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint1.getLeftOperand()); + final var operator1 = constraint1.getOperator(); + assertTrue((BinaryOperator.BEFORE.equals(operator1)) + || (BinaryOperator.AFTER.equals(operator1))); + assertTrue(isValidDate(constraint1.getRightOperand().getValue())); + + final var constraint2 = (Constraint) rule.getConstraint().get(1); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, constraint2.getLeftOperand()); + final var operator2 = constraint2.getOperator(); + assertTrue((BinaryOperator.BEFORE.equals(operator2)) + || (BinaryOperator.AFTER.equals(operator2))); + assertTrue(isValidDate(constraint2.getRightOperand().getValue())); + + final var duty = ((Permission) rule).getPostDuty().get(0); + assertEquals(1, duty.getConstraint().size()); + + final var dutyConstraint = (Constraint) duty.getConstraint().get(0); + assertEquals(LeftOperand.POLICY_EVALUATION_TIME, dutyConstraint.getLeftOperand()); + final var dutyConstraintOperator = dutyConstraint.getOperator(); + assertEquals(BinaryOperator.TEMPORAL_EQUALS, dutyConstraintOperator); + assertTrue(isValidDate(dutyConstraint.getRightOperand().getValue())); + } + + @Test + public void buildUsageLoggingRule_returnPermissionWithPostDuty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildUsageLoggingRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(1, ((Permission) rule).getPostDuty().size()); + + final var dutyAction = ((Permission) rule).getPostDuty().get(0).getAction().get(0); + assertEquals(Action.LOG, dutyAction); + } + + @Test + public void buildUsageNotificationRule_returnPermissionWithPostDuty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildUsageNotificationRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(1, ((Permission) rule).getPostDuty().size()); + + final var duty = ((Permission) rule).getPostDuty().get(0); + final var dutyAction = duty.getAction().get(0); + assertEquals(Action.NOTIFY, dutyAction); + assertEquals(1, duty.getConstraint().size()); + + final var dutyConstraint = (Constraint) duty.getConstraint().get(0); + assertEquals(LeftOperand.ENDPOINT, dutyConstraint.getLeftOperand()); + + final var dutyConstraintOperator = dutyConstraint.getOperator(); + assertEquals(BinaryOperator.DEFINES_AS, dutyConstraintOperator); + + assertTrue(isValidUri(dutyConstraint.getRightOperand().getValue())); + } + + @Test + public void buildConnectorRestrictedUsageRule_returnPermissionWithPostDuty() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var rule = PatternUtils.buildConnectorRestrictedUsageRule(); + + /* ASSERT */ + assertTrue(Permission.class.isAssignableFrom(rule.getClass())); + assertEquals(Action.USE, rule.getAction().get(0)); + assertEquals(1, rule.getConstraint().size()); + + final var constraint = (Constraint) rule.getConstraint().get(0); + assertEquals(LeftOperand.SYSTEM, constraint.getLeftOperand()); + assertEquals(BinaryOperator.SAME_AS, constraint.getOperator()); + assertTrue(isValidUri(constraint.getRightOperand().getValue())); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private boolean isValidInteger(String string) { + try { + Integer.parseInt(string); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + private boolean isValidDuration(String string) { + try { + Duration.parse(string); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + private boolean isValidDate(String string) { + try { + sdf.parse(string); + return true; + } catch (ParseException e) { + return false; + } + } + + private boolean isValidUri(String string) { + try { + URI.create(string); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/RuleUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/RuleUtilsTest.java new file mode 100644 index 000000000..75d5fad2b --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/RuleUtilsTest.java @@ -0,0 +1,1150 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.Action; +import de.fraunhofer.iais.eis.BinaryOperator; +import de.fraunhofer.iais.eis.ConstraintBuilder; +import de.fraunhofer.iais.eis.ContractRequestBuilder; +import de.fraunhofer.iais.eis.Duty; +import de.fraunhofer.iais.eis.DutyBuilder; +import de.fraunhofer.iais.eis.LeftOperand; +import de.fraunhofer.iais.eis.Permission; +import de.fraunhofer.iais.eis.PermissionBuilder; +import de.fraunhofer.iais.eis.Prohibition; +import de.fraunhofer.iais.eis.ProhibitionBuilder; +import de.fraunhofer.iais.eis.Rule; +import de.fraunhofer.iais.eis.util.RdfResource; +import de.fraunhofer.iais.eis.util.Util; +import io.dataspaceconnector.model.Contract; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import java.net.URI; +import java.text.ParseException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RuleUtilsTest { + + @Test + public void extractRulesFromContract_contractWithoutRules_returnEmptyList() { + /* ARRANGE */ + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + .build(); + + /* ACT */ + final var result = ContractUtils.extractRulesFromContract(contract); + + /* ASSERT */ + assertEquals(0, result.size()); + } + + @Test + public void extractRulesFromContract_contractWithThreeRules_returnRuleList() { + /* ARRANGE */ + final var permission = (Permission) getRuleThree(); + final var prohibition = (Prohibition) getRuleTwo(); + final var obligation = (Duty) getRuleOne(); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition)) + ._obligation_(Util.asList(obligation)) + .build(); + + /* ACT */ + final var result = ContractUtils.extractRulesFromContract(contract); + + /* ASSERT */ + assertEquals(3, result.size()); + assertTrue(result.contains(permission)); + assertTrue(result.contains(prohibition)); + assertTrue(result.contains(obligation)); + } + + @Test + public void extractRulesFromContract_contractWithTwoProhibitions_returnRuleList() { + /* ARRANGE */ + final var permission = (Permission) getRuleThree(); + final var prohibition = (Prohibition) getRuleTwo(); + final var obligation = (Duty) getRuleOne(); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition, prohibition)) + ._obligation_(Util.asList(obligation)) + .build(); + + /* ACT */ + final var result = ContractUtils.extractRulesFromContract(contract); + + /* ASSERT */ + assertEquals(4, result.size()); + assertTrue(result.contains(permission)); + assertTrue(result.contains(prohibition)); + assertTrue(result.contains(obligation)); + } + + @Test + public void extractRulesFromContract_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> ContractUtils.extractRulesFromContract(null)); + } + + @Test + public void getRulesForTargetId_matchingTargetIdForOneRule_returnRuleList() { + /* ARRANGE */ + final var target = URI.create("https://target"); + final var permission = getPermissionWithTarget(target); + final var prohibition = getProhibitionWithTarget(null); + final var obligation = getDutyWithTarget(null); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition)) + ._obligation_(Util.asList(obligation)) + .build(); + + /* ACT */ + final var result = ContractUtils.getRulesForTargetId(contract, target); + + /* ASSERT */ + assertEquals(1, result.size()); + assertTrue(result.contains(permission)); + } + + @Test + public void getRulesForTargetId_matchingTargetIdForMultipleRules_returnRuleList() { + /* ARRANGE */ + final var target = URI.create("https://target"); + final var permission = getPermissionWithTarget(target); + final var prohibition = getProhibitionWithTarget(null); + final var prohibition2 = getProhibitionWithTarget(target); + final var obligation = getDutyWithTarget(target); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition, prohibition2)) + ._obligation_(Util.asList(obligation)) + .build(); + + /* ACT */ + final var result = ContractUtils.getRulesForTargetId(contract, target); + + /* ASSERT */ + assertEquals(3, result.size()); + assertTrue(result.contains(permission)); + assertTrue(result.contains(prohibition2)); + assertTrue(result.contains(obligation)); + } + + @Test + public void getRulesForTargetId_noMatchingTargetId_returnRuleList() { + /* ARRANGE */ + final var target = URI.create("https://target"); + final var permission = getPermissionWithTarget(null); + final var prohibition = getProhibitionWithTarget(null); + final var obligation = getDutyWithTarget(null); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition)) + ._obligation_(Util.asList(obligation)) + .build(); + + /* ACT */ + final var result = ContractUtils.getRulesForTargetId(contract, target); + + /* ASSERT */ + assertEquals(0, result.size()); + } + + @Test + public void getRulesForTargetId_emptyContract_throwIllegalArgumentException() { + /* ARRANGE */ + final var target = URI.create("https://target"); + + /* ACT & ASSERT */ + assertThrows(IllegalArgumentException.class, () -> ContractUtils.getRulesForTargetId(null, + target)); + } + + @Test + public void getTargetRuleMap_listWithValidRulesAndTargets_returnMap() { + /* ARRANGE */ + final var target_1 = URI.create("https://target1"); + final var target_2 = URI.create("https://target2"); + final var target_3 = URI.create("https://target3"); + final var permission = getPermissionWithTarget(target_1); + final var prohibition = getProhibitionWithTarget(target_2); + final var obligation = getDutyWithTarget(target_3); + final var list = List.of(permission, prohibition, obligation); + + /* ACT */ + final var result = ContractUtils.getTargetRuleMap(list); + + /* ASSERT */ + assertEquals(3, result.keySet().size()); + assertEquals(3, result.entrySet().size()); + assertTrue(result.containsKey(target_1)); + assertTrue(result.containsKey(target_2)); + assertTrue(result.containsKey(target_3)); + assertTrue(result.get(target_1).contains(permission)); + assertTrue(result.get(target_2).contains(prohibition)); + assertTrue(result.get(target_3).contains(obligation)); + } + + @Test + public void getTargetRuleMap_listWithMultipleRulesForOneTarget_returnMap() { + /* ARRANGE */ + final var target_1 = URI.create("https://target1"); + final var target_2 = URI.create("https://target2"); + final var permission = getPermissionWithTarget(target_1); + final var prohibition = getProhibitionWithTarget(target_2); + final var obligation = getDutyWithTarget(target_2); + final var list = List.of(permission, prohibition, obligation); + + /* ACT */ + final var result = ContractUtils.getTargetRuleMap(list); + + /* ASSERT */ + assertEquals(2, result.keySet().size()); + assertEquals(2, result.entrySet().size()); + assertTrue(result.containsKey(target_1)); + assertTrue(result.containsKey(target_2)); + assertTrue(result.get(target_1).contains(permission)); + assertTrue(result.get(target_2).contains(prohibition)); + assertTrue(result.get(target_2).contains(obligation)); + assertEquals(2, result.get(target_2).size()); + } + + @Test + public void getTargetRuleMap_listWithRulesWithoutTargets_returnMap() { + /* ARRANGE */ + final var permission = (Permission) getRuleThree(); + final var prohibition = (Prohibition) getRuleTwo(); + final var obligation = (Duty) getRuleOne(); + final var contract = new ContractRequestBuilder() + ._contractStart_(getGregorianNow()) + ._permission_(Util.asList(permission)) + ._prohibition_(Util.asList(prohibition, prohibition)) + ._obligation_(Util.asList(obligation)) + .build(); + } + + @Test + public void compareRules_null_returnTrue() { + /* ACT && ASSERT */ + assertTrue(RuleUtils.compareRules(null, null)); + } + + @Test + public void compareRules_leftNull_returnFalse() { + /* ACT && ASSERT */ + assertFalse(RuleUtils.compareRules(null, new ArrayList<>())); + } + + @Test + public void compareRules_rightNull_returnFalse() { + /* ACT && ASSERT */ + assertFalse(RuleUtils.compareRules(new ArrayList<>(), null)); + } + + @Test + public void compareRules_sameList_returnTrue() { + /* ACT && ASSERT */ + assertTrue(RuleUtils.compareRules(Util.asList(getRuleOne(), getRuleTwo()), + Util.asList(getRuleOne(), getRuleTwo()))); + } + + @Test + public void compareRules_sameSets_returnTrue() { + /* ACT && ASSERT */ + assertTrue(RuleUtils.compareRules(Util.asList(getRuleOne(), getRuleTwo(), getRuleOne()) + , Util.asList(getRuleOne(), getRuleTwo()))); + } + + @Test + public void compareRules_differentSets_returnFalse() { + /* ACT && ASSERT */ + assertFalse(RuleUtils.compareRules(Util.asList(getRuleOne(), getRuleTwo(), + getRuleOne()), Util.asList(getRuleOne(), getRuleThree()))); + } + + /** + * removeContractsWithInvalidConsumer + */ + @Test + public void removeContractsWithInvalidConsumer_sameConsumer_removeNothing() { + /* ARRANGE */ + final var issuer = URI.create("https://someConsumer"); + final var list = List.of(getContractWithConsumer(), getContractWithoutConsumer()); + + /* ACT */ + final var result = ContractUtils.removeContractsWithInvalidConsumer(list, issuer); + + /* ASSERT */ + assertEquals(list, result); + } + + @Test + public void removeContractsWithInvalidConsumer_differentConsumer_removeRestrictedOffer() { + /* ARRANGE */ + final var issuer = URI.create("https://someOtherConsumer"); + final var list = List.of(getContractWithConsumer(), getContractWithoutConsumer()); + + /* ACT */ + final var result = ContractUtils.removeContractsWithInvalidConsumer(list, issuer); + + /* ASSERT */ + assertEquals(List.of(getContractWithoutConsumer()), result); + } + + @Test + public void removeContractsWithInvalidConsumer_nullList_throwIllegalArgumentException() { + /* ARRANGE */ + final var issuer = URI.create("https://someOtherConsumer"); + + /* ACT && ASSERT*/ + assertThrows(IllegalArgumentException.class, + () -> ContractUtils.removeContractsWithInvalidConsumer(null, issuer)); + } + + @Test + public void removeContractsWithInvalidConsumer_nullIssuer_throwIllegalArgumentException() { + /* ARRANGE */ + final var list = List.of(getContractWithConsumer(), getContractWithoutConsumer()); + + /* ACT && ASSERT*/ + assertThrows(IllegalArgumentException.class, + () -> ContractUtils.removeContractsWithInvalidConsumer(list, null)); + } + + @Test + public void removeContractsWithInvalidConsumer_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT*/ + assertThrows(IllegalArgumentException.class, + () -> ContractUtils.removeContractsWithInvalidConsumer(null, null)); + } + + @Test + public void getMaxAccess_inputNull_throwNullPointerException() { + /* ACT & ASSERT */ + assertThrows(NullPointerException.class, () -> RuleUtils.getMaxAccess(null)); + } + + @Test + public void getMaxAccess_inputCorrectOperatorEquals_returnAccessInteger() { + /* ARRANGE */ + final var maxAccess = 2; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource(String.valueOf(maxAccess), URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getMaxAccess(permission); + + /* ASSERT */ + assertEquals(maxAccess, result); + } + + @Test + public void getMaxAccess_inputCorrectOperatorLessThanEquals_returnAccessInteger() { + /* ARRANGE */ + final var maxAccess = 2; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.LTEQ) + ._rightOperand_(new RdfResource(String.valueOf(maxAccess), URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getMaxAccess(permission); + + /* ASSERT */ + assertEquals(maxAccess, result); + } + + @Test + public void getMaxAccess_inputCorrectOperatorLessThan_returnAccessInteger() { + /* ARRANGE */ + final var maxAccess = 2; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.LT) + ._rightOperand_(new RdfResource(String.valueOf(maxAccess), URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getMaxAccess(permission); + + /* ASSERT */ + assertEquals(maxAccess - 1, result); + } + + @Test + public void getMaxAccess_inputInvalidAccessBiggerThanMaxInteger_returnSomething() { + /* ARRANGE */ + final var maxAccess = Integer.MAX_VALUE + 1; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource(String.valueOf(maxAccess), URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getMaxAccess(permission); + + /* ASSERT */ + assertTrue(result >= 0); + } + + @Test + public void getMaxAccess_inputInvalidAccessNotInteger_throwNumberFormatException() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("I am not an integer.", URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT & ASSERT */ + assertThrows(NumberFormatException.class, () -> RuleUtils.getMaxAccess(permission)); + } + + @Test + public void getMaxAccess_inputInvalidAccessNegative_returnZero() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("-3", URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getMaxAccess(permission); + + /* ASSERT */ + assertEquals(0, result); + } + + @Test + public void getMaxAccess_inputInvalidMaxAccessConstraintNotFirstInList_throwNumberFormatException() { + /* ARRANGE */ + final var maxAccess = 3; + + final var constraint1 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource("P6M", URI.create("xsd:duration"))) + .build(); + final var constraint2 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource(String.valueOf(maxAccess), URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint1, constraint2)) + .build(); + + /* ACT & ASSERT */ + assertThrows(NumberFormatException.class, () -> RuleUtils.getMaxAccess(permission)); + } + + @Test + public void getTimeInterval_inputNull_throwNullPointerException() { + /* ACT & ASSERT */ + assertThrows(NullPointerException.class, () -> RuleUtils.getTimeInterval(null)); + } + + @Test + public void getTimeInterval_inputCorrect_returnTimeInterval() throws ParseException { + /* ARRANGE */ + final var startDate = "2021-01-01T00:00:00Z"; + final var endDate = "2022-01-01T00:00:00Z"; + + final var startConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(startDate, URI.create("xsd:dateTimeStamp"))) + .build(); + final var endConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.BEFORE) + ._rightOperand_(new RdfResource(endDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(startConstraint, endConstraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertEquals(ZonedDateTime.parse(startDate), result.getStart()); + assertEquals(ZonedDateTime.parse(endDate), result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidWrongConstraintType_returnNull() throws ParseException { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource("P6M", URI.create("xsd:duration"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertNull(result.getStart()); + assertNull(result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidNoStartDate_returnCorrectOutput() throws ParseException { + /* ARRANGE */ + final var endDate = "2022-01-01T00:00:00Z"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.BEFORE) + ._rightOperand_(new RdfResource(endDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertNull(result.getStart()); + assertEquals(ZonedDateTime.parse(endDate), result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidNoEndDate_returnCorrectOutput() throws ParseException { + /* ARRANGE */ + final var startDate = "2021-01-01T00:00:00Z"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(startDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertEquals(ZonedDateTime.parse(startDate), result.getStart()); + assertNull(result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidStartAfterEnd_returnCorrectOutput() throws ParseException { + /* ARRANGE */ + final var startDate = "2022-01-01T00:00:00Z"; + final var endDate = "2021-01-01T00:00:00Z"; + + final var startConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(startDate, URI.create("xsd:dateTimeStamp"))) + .build(); + final var endConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.BEFORE) + ._rightOperand_(new RdfResource(endDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(startConstraint, endConstraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertEquals(ZonedDateTime.parse(startDate), result.getStart()); + assertEquals(ZonedDateTime.parse(endDate), result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidWrongOperator_returnNull() throws ParseException { + /* ARRANGE */ + final var startDate = "2021-01-01T00:00:00Z"; + final var endDate = "2022-01-01T00:00:00Z"; + + final var startConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.CONTAINS) + ._rightOperand_(new RdfResource(startDate, URI.create("xsd:dateTimeStamp"))) + .build(); + final var endConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.CONTAINS) + ._rightOperand_(new RdfResource(endDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(startConstraint, endConstraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertNull(result.getStart()); + assertNull(result.getEnd()); + } + + @Test + public void getTimeInterval_inputInvalidWrongDateFormat_returnNull() throws ParseException { + /* ARRANGE */ + final var startDate = "2021-01-01T00:00:00.000"; + final var endDate = "2022-01-01T00:00:00.000"; + + final var startConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.CONTAINS) + ._rightOperand_(new RdfResource(startDate, URI.create("xsd:dateTimeStamp"))) + .build(); + final var endConstraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.CONTAINS) + ._rightOperand_(new RdfResource(endDate, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(startConstraint, endConstraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getTimeInterval(permission); + + /* ASSERT */ + assertNull(result.getStart()); + assertNull(result.getEnd()); + } + + @Test + public void getEndpoint_inputNull_throwNullPointerException() { + /* ACT & ASSERT */ + assertThrows(NullPointerException.class, () -> RuleUtils.getEndpoint(null)); + } + + @Test + public void getEndpoint_inputCorrect_returnEndpoint() { + /* ARRANGE */ + final var endpoint = "https://localhost:8000/notify"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ENDPOINT) + ._operator_(BinaryOperator.DEFINES_AS) + ._rightOperand_(new RdfResource(endpoint, URI.create("xsd:anyURI"))) + .build(); + + final var duty = new DutyBuilder() + ._action_(Util.asList(Action.NOTIFY)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getEndpoint(duty); + + /* ASSERT */ + assertEquals(endpoint, result); + } + + @Test + public void getEndpoint_inputInvalidWrongConstraintType_returnValue() { + /* ARRANGE */ + final var value = "5"; + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource(value, URI.create("xsd:decimal"))) + .build(); + + final var duty = new DutyBuilder() + ._action_(Util.asList(Action.NOTIFY)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getEndpoint(duty); + + /* ASSERT */ + assertEquals(value, result); + } + + @Test + public void getEndpoint_inputInvalidNotificationConstraintNotFirstInList_returnWrongValue() { + /* ARRANGE */ + final var endpoint = "https://localhost:8000/notify"; + + final var constraint1 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + .build(); + final var constraint2 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ENDPOINT) + ._operator_(BinaryOperator.DEFINES_AS) + ._rightOperand_(new RdfResource(endpoint, URI.create("xsd:anyURI"))) + .build(); + + final var duty = new DutyBuilder() + ._action_(Util.asList(Action.NOTIFY)) + ._constraint_(Util.asList(constraint1, constraint2)) + .build(); + + /* ACT */ + final var result = RuleUtils.getEndpoint(duty); + + /* ASSERT */ + assertNotEquals(endpoint, result); + } + + @Test + public void getPipEndpoint_inputNull_throwNullPointerException() { + /* ACT & ASSERT */ + assertThrows(NullPointerException.class, () -> RuleUtils.getPipEndpoint(null)); + } + + @Test + public void getPipEndpoint_inputCorrect_returnPipEndpoint() { + /* ARRANGE */ + final var pipEndpoint = URI.create("https://pip.com"); + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + ._pipEndpoint_(pipEndpoint) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getPipEndpoint(permission); + + /* ASSERT */ + assertEquals(pipEndpoint, result); + } + + @Test + public void getPipEndpoint_inputInvalidConstraintHasNoPipEndpoint_returnNull() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getPipEndpoint(permission); + + /* ASSERT */ + assertNull(result); + } + + @Test + public void getDate_inputNull_throwNullPointerException() { + /* ACT & ASSERT*/ + assertThrows(NullPointerException.class, () -> RuleUtils.getDate(null)); + } + + @Test + public void getDate_inputCorrect_returnDate() { + /* ARRANGE */ + final var date = "2021-01-01T00:00:00Z"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(date, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getDate(permission); + + /* ASSERT */ + assertEquals(ZonedDateTime.parse(date), result); + } + + @Test + public void getDate_inputInvalidWrongConstraintType_throwDateTimeParseException() { + /* ARRANGE */ + final var date = "2021-01-01T00:00:00Z"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.EQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT & ASSERT */ + assertThrows(DateTimeParseException.class, () -> RuleUtils.getDate(permission)); + } + + @Test + public void getDate_inputInvalidWrongDateFormat_throwDateTimeParseException() { + /* ARRANGE */ + final var date = "2021-01-01T00:00:00.000"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(date, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT & ASSERT */ + assertThrows(DateTimeParseException.class, () -> RuleUtils.getDate(permission)); + } + + @Test + public void getDate_inputInvalidNotADate_throwDateTimeParseException() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource("I am not a date.", URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + assertThrows(DateTimeParseException.class, () -> RuleUtils.getDate(permission)); + } + + @Test + public void getDate_inputInvalidDateConstraintNotFirstInList_throwDateTimeParseException() { + /* ARRANGE */ + final var date = "2021-01-01T00:00:00Z"; + + final var constraint1 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource("P6M", URI.create("xsd:duration"))) + .build(); + final var constraint2 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.POLICY_EVALUATION_TIME) + ._operator_(BinaryOperator.AFTER) + ._rightOperand_(new RdfResource(date, URI.create("xsd:dateTimeStamp"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint1, constraint2)) + .build(); + + /* ACT & ASSERT */ + assertThrows(DateTimeParseException.class, () -> RuleUtils.getDate(permission)); + } + + @Test + public void getDuration_inputNull_throwNullPointerException() { + /* ACT & ASSERT*/ + assertThrows(NullPointerException.class, () -> RuleUtils.getDuration(null)); + } + + @Test + public void getDuration_inputCorrect_returnDuration() throws DatatypeConfigurationException { + /* ARRANGE */ + final var duration = "PT1M30.5S"; + + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource(duration, URI.create("xsd:duration"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getDuration(permission); + + /* ASSERT */ + assertEquals(DatatypeFactory.newInstance().newDuration(duration).toString(), result.toString()); + } + + @Test + public void getDuration_inputInvalidWrongConstraintType_returnNull() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.LTEQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT */ + final var result = RuleUtils.getDuration(permission); + + /* ASSERT */ + assertNull(result); + } + + @Test + public void getDuration_inputInvalidNotADuration_throwDateTimeParseException() { + /* ARRANGE */ + final var constraint = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource("I am not a duration.", URI.create("xsd:duration"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint)) + .build(); + + /* ACT & ASSERT */ + assertThrows(DateTimeParseException.class, () -> RuleUtils.getDuration(permission)); + } + + @Test + public void getDuration_inputInvalidDurationConstraintNotFirstInList_returnNull() { + /* ARRANGE */ + final var duration = "P6M"; + + final var constraint1 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.COUNT) + ._operator_(BinaryOperator.LTEQ) + ._rightOperand_(new RdfResource("5", URI.create("xsd:decimal"))) + .build(); + final var constraint2 = new ConstraintBuilder() + ._leftOperand_(LeftOperand.ELAPSED_TIME) + ._operator_(BinaryOperator.SHORTER_EQ) + ._rightOperand_(new RdfResource(duration, URI.create("xsd:duration"))) + .build(); + + final var permission = new PermissionBuilder() + ._action_(Util.asList(Action.USE)) + ._constraint_(Util.asList(constraint1, constraint2)) + .build(); + + /*ACT*/ + final var result = RuleUtils.getDuration(permission); + + /*ASSERT*/ + assertNull(result); + } + + /** + * Utilities + */ + + public Rule getRuleOne() { + return new DutyBuilder() + ._action_(Util.asList(getActionOne())) + .build(); + } + + public Rule getRuleTwo() { + return new ProhibitionBuilder() + ._action_(Util.asList(getActionsTwo())) + .build(); + } + + public Rule getRuleThree() { + return new PermissionBuilder() + ._action_(Util.asList(getActionThree())) + .build(); + } + + public Action getActionOne() { + return Action.USE; + } + + public Action getActionsTwo() { + return Action.NOTIFY; + } + + public Action getActionThree() { + return Action.LOG; + } + + private Prohibition getProhibitionWithTarget(final URI target) { + return new ProhibitionBuilder() + ._action_(Util.asList(getActionsTwo())) + ._target_(target) + .build(); + } + + private Permission getPermissionWithTarget(final URI target) { + return new PermissionBuilder() + ._action_(Util.asList(getActionThree())) + ._target_(target) + .build(); + } + + private Duty getDutyWithTarget(final URI target) { + return new DutyBuilder() + ._action_(Util.asList(getActionOne())) + ._target_(target) + .build(); + } + + @SneakyThrows + private Contract getContractWithoutConsumer() { + final var constructor = Contract.class.getConstructor(); + constructor.setAccessible(true); + + final var contract = constructor.newInstance(); + + final var titleField = contract.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(contract, "Catalog without consumer"); + + final var issuerField = contract.getClass().getDeclaredField("consumer"); + issuerField.setAccessible(true); + issuerField.set(contract, URI.create("")); + + return contract; + } + + @SneakyThrows + private Contract getContractWithConsumer() { + final var constructor = Contract.class.getConstructor(); + constructor.setAccessible(true); + + final var contract = constructor.newInstance(); + + final var titleField = contract.getClass().getDeclaredField("title"); + titleField.setAccessible(true); + titleField.set(contract, "Catalog with consumer"); + + final var issuerField = contract.getClass().getDeclaredField("consumer"); + issuerField.setAccessible(true); + issuerField.set(contract, URI.create("https://someConsumer")); + + return contract; + } + } diff --git a/src/test/java/io/dataspaceconnector/utils/TemplateUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/TemplateUtilsTest.java new file mode 100644 index 000000000..8170a9643 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/TemplateUtilsTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import de.fraunhofer.iais.eis.ArtifactBuilder; +import de.fraunhofer.iais.eis.RepresentationBuilder; +import de.fraunhofer.iais.eis.ResourceBuilder; +import de.fraunhofer.iais.eis.util.Util; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class TemplateUtilsTest { + + @Test + public void getRepresentationTemplates_validInput_returnRepresentationList() { + /* ARRANGE */ + final var requestedArtifact = URI.create("https://requestedArtifact"); + final var representation = new RepresentationBuilder() + ._instance_(Util.asList(new ArtifactBuilder(requestedArtifact).build(), new ArtifactBuilder().build())) + .build(); + final var requestedArtifacts = List.of(requestedArtifact); + final var download = true; + final var remoteUrl = URI.create("https://remoteAddress"); + final var resource = new ResourceBuilder() + ._representation_(Util.asList(representation)) + .build(); + + /* ACT */ + final var result = TemplateUtils + .getRepresentationTemplates(resource, requestedArtifacts, download, remoteUrl); + + /* ASSERT */ + assertEquals(1, result.size()); + + } + + @Test + public void getRepresentationTemplates_missingRepresentations_returnEmptyList() { + /* ARRANGE */ + final var resource = new ResourceBuilder().build(); + final var requestedArtifacts = List.of(URI.create("https://requestedArtifact")); + final var download = true; + final var remoteUrl = URI.create("https://remoteAddress"); + + /* ACT */ + final var result = TemplateUtils.getRepresentationTemplates(resource, + requestedArtifacts, download, remoteUrl); + + /* ASSERT */ + assertEquals(0, result.size()); + } + + @Test + public void getArtifactTemplates_validInputsUnequalId_returnEmptyList() { + /* ARRANGE */ + final var representation = new RepresentationBuilder() + ._instance_(Util.asList(new ArtifactBuilder().build())) + .build(); + final var requestedArtifacts = List.of(URI.create("https://requestedArtifact")); + final var download = true; + final var remoteUrl = URI.create("https://remoteAddress"); + + /* ACT */ + final var result = TemplateUtils.getArtifactTemplates(representation, + requestedArtifacts, download, remoteUrl); + + /* ASSERT */ + assertEquals(0, result.size()); + } + + @Test + public void getArtifactTemplates_validInputsMatchingId_returnArtifactList() { + /* ARRANGE */ + final var requestedArtifact = URI.create("https://requestedArtifact"); + final var representation = new RepresentationBuilder() + ._instance_(Util.asList(new ArtifactBuilder(requestedArtifact).build())) + .build(); + final var requestedArtifacts = List.of(requestedArtifact); + final var download = true; + final var remoteUrl = URI.create("https://remoteAddress"); + + /* ACT */ + final var result = TemplateUtils.getArtifactTemplates(representation, + requestedArtifacts, download, remoteUrl); + + /* ASSERT */ + assertEquals(1, result.size()); + final var template = result.get(0); + assertNull(template.getOldRemoteId()); + final var desc = template.getDesc(); + assertEquals(requestedArtifact, desc.getRemoteId()); + assertEquals(remoteUrl, desc.getRemoteAddress()); + assertNull(desc.getTitle()); + assertNull(desc.getAccessUrl()); + assertNull(desc.getUsername()); + assertNull(desc.getPassword()); + assertNull(desc.getValue()); + assertEquals(download, desc.isAutomatedDownload()); + assertEquals(0, desc.getAdditional().size()); + } + + @Test + public void getArtifactTemplates_missingRepresentationInstance_returnEmptyList() { + /* ARRANGE */ + final var representation = new RepresentationBuilder().build(); + final var requestedArtifacts = List.of(URI.create("https://requestedArtifact")); + final var download = true; + final var remoteUrl = URI.create("https://remoteAddress"); + + /* ACT */ + final var result = TemplateUtils.getArtifactTemplates(representation, + requestedArtifacts, download, remoteUrl); + + /* ASSERT */ + assertEquals(0, result.size()); + } +} diff --git a/src/test/java/io/dataspaceconnector/utils/UUIDUtilsTests.java b/src/test/java/io/dataspaceconnector/utils/UUIDUtilsTests.java new file mode 100644 index 000000000..f392c1daf --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/UUIDUtilsTests.java @@ -0,0 +1,259 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import io.dataspaceconnector.exceptions.UUIDCreationException; +import io.dataspaceconnector.exceptions.UUIDFormatException; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.UUID; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UUIDUtilsTests { + + @Test + public void findUuids_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> UUIDUtils.findUuids(null)); + } + + @Test + public void findUuids_oneUuidInInput_returnUuid() { + /* ARRANGE */ + final var uuidString = "c5af2999-7a7f-4cc5-9ce1-4531c60a7151"; + final var input = "String with UUID: " + uuidString; + + /* ACT */ + final var uuids = UUIDUtils.findUuids(input); + + /* ASSERT */ + assertEquals(1, uuids.size()); + assertTrue(uuids.contains(uuidString)); + } + + @Test + public void findUuids_twoUuidsInInput_returnUuids() { + /* ARRANGE */ + final var uuidString = "e5e2ab04-633a-44b9-87d9-a097ae6da3cf"; + final var uuidString2 = "c5af2999-7a7f-4cc5-9ce1-4531c60a7151"; + final var input = "String with UUIDs: " + uuidString + " and " + uuidString2; + + /* ACT */ + final var uuids = UUIDUtils.findUuids(input); + + /* ASSERT */ + assertEquals(2, uuids.size()); + assertTrue(uuids.contains(uuidString)); + assertTrue(uuids.contains(uuidString2)); + } + + @Test + public void findUuids_noUuidInInput_returnEmptyList() { + /* ARRANGE */ + final var input = "String without UUID"; + + /* ACT */ + final var uuids = UUIDUtils.findUuids(input); + + /* ASSERT */ + assertTrue(uuids.isEmpty()); + } + + @Test + public void findUuids_oneDigitMissingForUuidInInput_returnEmptyList() { + /* ARRANGE */ + final var input = "String without UUID: c5af2999-7a7f-4cc5-9ce1-4531c60a715"; + + /* ACT */ + final var uuids = UUIDUtils.findUuids(input); + + /* ASSERT */ + assertTrue(uuids.isEmpty()); + } + + @Test + public void uuidFromUri_uriWithoutUuid_throwUUIDFormatException() { + /* ARRANGE */ + final var uuidString = ""; + final var uriString = "https://dsc/anon42/api/" + uuidString; + + final var inputUri = URI.create(uriString); + + /* ACT && ASSERT */ + assertThrows(UUIDFormatException.class, () -> UUIDUtils.uuidFromUri(inputUri)); + } + + @Test + public void uuidFromUri_uriWithOneUuid_returnUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uriString = "https://dsc/anon42/api/" + uuidString; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithTwoSeparatedUuids_returnLastUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uuidString2 = "4ad9a9ba-1a0b-4012-87de-13a8734e3765"; + final var uriString = "https://dsc/anon42/api/" + uuidString + "/" + uuidString2; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString2); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithTwoCombinedUuids_returnLastUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uuidString2 = "4ad9a9ba-1a0b-4012-87de-13a8734e3765"; + final var uriString = "https://dsc/anon42/api/" + uuidString + uuidString2; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString2); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithOneUuidCorrectIndex_returnUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uriString = "https://dsc/anon42/api/" + uuidString; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri, 0); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithTwoUuidsIndexZero_returnFirstUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uuidString2 = "4ad9a9ba-1a0b-4012-87de-13a8734e3765"; + final var uriString = "https://dsc/anon42/api/" + uuidString + uuidString2; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri, 0); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithTwoUuidsIndexOne_returnSecondUuid() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uuidString2 = "4ad9a9ba-1a0b-4012-87de-13a8734e3765"; + final var uriString = "https://dsc/anon42/api/" + uuidString + uuidString2; + + final var inputUri = URI.create(uriString); + final var expectedUUID = UUID.fromString(uuidString2); + + /* ACT */ + final var resultUUID = UUIDUtils.uuidFromUri(inputUri, 1); + + /* ASSERT */ + assertEquals(resultUUID, expectedUUID); + } + + @Test + public void uuidFromUri_uriWithOneUuidInvalidIndex_throwIndexOutOfBoundsException() { + /* ARRANGE */ + final var uuidString = "b7c9d390-837b-4edc-b47c-877c3d3570f0"; + final var uriString = "https://dsc/anon42/api/" + uuidString; + + final var inputUri = URI.create(uriString); + + /* ACT && ASSERT */ + assertThrows(IndexOutOfBoundsException.class, () -> UUIDUtils.uuidFromUri(inputUri, 1)); + } + + @Test + public void createUuid_functionFalseForAllUuids_returnUuid() { + /* ARRANGE */ + final Function function = uuid -> false; + long maxNumTries = 10; + + /* ACT */ + final var uuid = UUIDUtils.createUUID(function, maxNumTries); + + /* ASSERT */ + assertNotNull(uuid); + } + + @Test + public void createUuid_functionTrueForAllUuids_throwUUIDCreationException() { + /* ARRANGE */ + final Function function = uuid -> true; + long maxNumTries = 10; + + /* ACT && ASSERT */ + assertThrows(UUIDCreationException.class, () -> UUIDUtils.createUUID(function, maxNumTries)); + } + + @Test + public void createUuid_functionNull_throwNullPointerException() { + /* ARRANGE */ + long maxNumTries = 10; + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> UUIDUtils.createUUID(null, maxNumTries)); + } + + @Test + public void createUuid_maxNumTriesNull_throwIllegalArgumentException() { + /* ARRANGE */ + final Function function = uuid -> false; + long maxNumTries = 0; + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> UUIDUtils.createUUID(function, maxNumTries)); + } + +} diff --git a/src/test/java/io/dataspaceconnector/utils/UtilsTest.java b/src/test/java/io/dataspaceconnector/utils/UtilsTest.java new file mode 100644 index 000000000..a8337bc97 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/UtilsTest.java @@ -0,0 +1,255 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class UtilsTest { + + /************************************************************************** + * requireNonNull. + *************************************************************************/ + + @Test + public void requireNonNull_nullObj_throwsIllegalArgumentException() { + /* ARRANGE */ + final var errorMsg = ErrorMessages.DESC_NULL; + + /* ACT && ASSERT */ + final var msg = assertThrows( + IllegalArgumentException.class, () -> Utils.requireNonNull(null, errorMsg)); + assertEquals(errorMsg.toString(), msg.getMessage()); + } + + @Test + public void requireNonNull_nullMsg_returnObj() { + /* ARRANGE */ + Integer obj = 5; + + /* ACT */ + final var value = Utils.requireNonNull(obj, null); + + /* ASSERT */ + assertEquals(5, value); + } + + @Test + public void requireNonNull_null_throwsNullPointerException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(NullPointerException.class, () -> Utils.requireNonNull(null, null)); + } + + @Test + public void requireNonNull_Valid_returnObj() { + /* ARRANGE */ + Integer obj = 5; + + /* ACT */ + final var value = Utils.requireNonNull(obj, ErrorMessages.DESC_NULL); + + /* ASSERT */ + assertEquals(5, value); + } + + /************************************************************************** + * toStream. + *************************************************************************/ + + @Test + public void toStream_null_returnEmptyStream() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT */ + final var result = Utils.toStream(null); + + /* ASSERT */ + assertEquals(Stream.empty().collect(Collectors.toList()), result.collect(Collectors.toList())); + } + + @Test + public void toStream_validCollection_returnStream() { + /* ARRANGE */ + final var list = List.of(1, 2, 3); + + /* ACT */ + final var result = Utils.toStream(list); + + /* ASSERT */ + assertEquals(list, result.collect(Collectors.toList())); + } + + /************************************************************************** + * toPage. + *************************************************************************/ + + @Test + public void toPage_nullList_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> Utils.toPage(null, Pageable.unpaged())); + } + + @Test + public void toPage_nullPageable_throwIllegalArgumentException() { + /* ARRANGE */ + final var list = new ArrayList(); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> Utils.toPage(list, null)); + } + + @Test + public void toPage_null_throwIllegalArgumentException() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, () -> Utils.toPage(null, null)); + } + + @Test + public void toPage_unpaged_returnAllElements() { + /* ARRANGE */ + final var list = List.of(5, 4, 3, 2); + + /* ACT */ + final var result = Utils.toPage(list, Pageable.unpaged()); + + /* ASSERT */ + assertEquals(list, result.toList()); + } + + @Test + public void toPage_paged_returnOnlyPage() { + /* ARRANGE */ + final var list = List.of(5, 4, 3, 2, 1); + final var pageable = PageRequest.of(1, 2); + + /* ACT */ + final var result = Utils.toPage(list, pageable); + + /* ASSERT */ + assertEquals(2, result.getSize()); + assertTrue(result.toList().contains(2)); + assertTrue(result.toList().contains(3)); + } + + /************************************************************************** + * toPageRequest. + *************************************************************************/ + + @Test + public void toPageRequest_null_defaultRequest() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = Utils.toPageRequest(null, null); + + /* ASSERT */ + assertEquals(PageRequest.of(Utils.DEFAULT_FIRST_PAGE, Utils.DEFAULT_PAGE_SIZE), result); + } + + @Test + public void toPageRequest_negativeValues_defaultRequest() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = Utils.toPageRequest(-500, -3); + + /* ASSERT */ + assertEquals(PageRequest.of(Utils.DEFAULT_FIRST_PAGE, Utils.DEFAULT_PAGE_SIZE), result); + } + + @Test + public void toPageRequest_sizeBiggerThenMaxSize_PageLimitedToMaxSize() { + /* ARRANGE */ + // Nothing to arrange. + + /* ACT */ + final var result = Utils.toPageRequest(Utils.DEFAULT_FIRST_PAGE, Utils.MAX_PAGE_SIZE + 20); + + /* ASSERT */ + assertEquals(PageRequest.of(Utils.DEFAULT_FIRST_PAGE, Utils.MAX_PAGE_SIZE), result); + } + + @Test + public void toPageRequest_validValues_returnRequestWithRandomValues() { + /* ARRANGE */ + final var page = 21; + final var size = 50; + + /* ACT */ + final var result = Utils.toPageRequest(page, size); + + /* ASSERT */ + assertEquals(PageRequest.of(page, size), result); + } + + /************************************************************************** + * DEFAULT_PAGE_SIZE. + *************************************************************************/ + @Test + public void DEFAULT_PAGE_SIZE_IS_30() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(30, Utils.DEFAULT_PAGE_SIZE); + } + + /************************************************************************** + * DEFAULT_FIRST_PAGE. + *************************************************************************/ + @Test + public void DEFAULT_FIRST_PAGE_IS_0() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(0, Utils.DEFAULT_FIRST_PAGE); + } + + /************************************************************************** + * MAX_PAGE_SIZE. + *************************************************************************/ + @Test + public void MAX_PAGE_SIZE_IS_100() { + /* ARRANGE */ + // Nothing to arrange here. + + /* ACT && ASSERT */ + assertEquals(100, Utils.MAX_PAGE_SIZE); + } +} diff --git a/src/test/java/io/dataspaceconnector/utils/ValidationUtilsTest.java b/src/test/java/io/dataspaceconnector/utils/ValidationUtilsTest.java new file mode 100644 index 000000000..b36e28330 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/utils/ValidationUtilsTest.java @@ -0,0 +1,349 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.dataspaceconnector.model.QueryInput; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ValidationUtilsTest { + + @Test + public void validateQueryInput_inputNull_passWithoutException() { + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(null)); + } + + @Test + public void validateQueryInput_validQueryInput_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_headersNull_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(null); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_headersEmpty_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(new ConcurrentHashMap<>()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_paramsNull_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(null); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_paramsEmpty_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(new ConcurrentHashMap<>()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_pathVariablesNull_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(null); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_pathVariablesEmpty_passWithoutException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(new ConcurrentHashMap<>()); + + /* ACT && ASSERT */ + assertDoesNotThrow(() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullKeyInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry(null, "value")); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyKeyInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry("", "value")); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankKeyInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry(" ", "value")); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullValueInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry("key", null)); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyValueInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry("key", "")); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankValueInHeaders_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getMapWithEntry("key", " ")); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullKeyInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry(null, "value")); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyKeyInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry("", "value")); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankKeyInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry(" ", "value")); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullValueInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry("key", null)); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyValueInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry("key", "")); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankValueInParams_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getMapWithEntry("key", " ")); + queryInput.setPathVariables(getValidMap()); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullKeyInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry(null, "value")); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyKeyInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry("", "value")); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankKeyInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry(" ", "value")); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_nullValueInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry("key", null)); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_emptyValueInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry("key", "")); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + @Test + public void validateQueryInput_blankValueInPathVariables_throwIllegalArgumentException() { + /* ARRANGE */ + QueryInput queryInput = new QueryInput(); + queryInput.setHeaders(getValidMap()); + queryInput.setParams(getValidMap()); + queryInput.setPathVariables(getMapWithEntry("key", " ")); + + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class ,() -> ValidationUtils.validateQueryInput(queryInput)); + } + + private Map getValidMap() { + Map map = new ConcurrentHashMap<>(); + map.put("validKey", "validValue"); + return map; + } + + private Map getMapWithEntry(String key, String value) { + //use Hashmap here as is allows null keys and values + Map map = new HashMap<>(); + map.put(key, value); + return map; + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/AgreementViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/AgreementViewAssemblerTest.java new file mode 100644 index 000000000..faddffa30 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/AgreementViewAssemblerTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.Agreement; +import io.dataspaceconnector.model.AgreementDesc; +import io.dataspaceconnector.model.AgreementFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +@SpringBootTest(classes = {AgreementViewAssembler.class, ViewAssemblerHelper.class, + AgreementFactory.class}) +public class AgreementViewAssemblerTest { + + @Autowired + private AgreementViewAssembler agreementViewAssembler; + + @Autowired + private AgreementFactory agreementFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.AgreementController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = agreementViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var agreementId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.AgreementController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = agreementViewAssembler.getSelfLink(agreementId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + agreementId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> agreementViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnAgreementView() { + /* ARRANGE */ + final var agreement = getAgreement(); + + /* ACT */ + final var result = agreementViewAssembler.toModel(agreement); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(agreement.getValue(), result.getValue()); + Assertions.assertEquals(agreement.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(agreement.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(agreement.getRemoteId(), result.getRemoteId()); + Assertions.assertEquals(agreement.isConfirmed(), result.isConfirmed()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getAgreementLink(agreement.getId()), selfLink.get().getHref()); + + final var artifactsLink = result.getLink("artifacts"); + assertTrue(artifactsLink.isPresent()); + assertNotNull(artifactsLink.get()); + assertEquals(getAgreementArtifactsLink(agreement.getId()), artifactsLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private Agreement getAgreement() { + final var desc = new AgreementDesc(); + desc.setValue("agreement value"); + desc.setConfirmed(true); + desc.setRemoteId(URI.create("https://agreement.com")); + final var agreement = agreementFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + + ReflectionTestUtils.setField(agreement, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(agreement, "creationDate", date); + ReflectionTestUtils.setField(agreement, "modificationDate", date); + + return agreement; + } + + private String getAgreementLink(final UUID agreementId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.AgreementController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + agreementId; + } + + private String getAgreementArtifactsLink(final UUID agreementId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.AgreementsToArtifacts.class) + .getResource(agreementId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/ArtifactViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/ArtifactViewAssemblerTest.java new file mode 100644 index 000000000..79df2b554 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/ArtifactViewAssemblerTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.Artifact; +import io.dataspaceconnector.model.ArtifactDesc; +import io.dataspaceconnector.model.ArtifactFactory; +import io.dataspaceconnector.model.QueryInput; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +@SpringBootTest(classes = {ArtifactViewAssembler.class, ViewAssemblerHelper.class, + ArtifactFactory.class}) +public class ArtifactViewAssemblerTest { + + @Autowired + private ArtifactViewAssembler artifactViewAssembler; + + @Autowired + private ArtifactFactory artifactFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ArtifactController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = artifactViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var artifactId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ArtifactController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = artifactViewAssembler.getSelfLink(artifactId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + artifactId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> artifactViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnArtifactView() { + /* ARRANGE */ + final var artifact = getArtifact(); + + /* ACT */ + final var result = artifactViewAssembler.toModel(artifact); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(artifact.getTitle(), result.getTitle()); + Assertions.assertEquals(artifact.getByteSize(), result.getByteSize()); + Assertions.assertEquals(artifact.getCheckSum(), result.getCheckSum()); + Assertions.assertEquals(artifact.getNumAccessed(), result.getNumAccessed()); + Assertions.assertEquals(artifact.getRemoteId(), result.getRemoteId()); + Assertions.assertEquals(artifact.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(artifact.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(artifact.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getArtifactLink(artifact.getId()), selfLink.get().getHref()); + + final var dataLink = result.getLink("data"); + assertTrue(dataLink.isPresent()); + assertNotNull(dataLink.get()); + assertEquals(getArtifactDataLink(artifact.getId()), dataLink.get().getHref()); + + final var representationsLink = result.getLink("representations"); + assertTrue(representationsLink.isPresent()); + assertNotNull(representationsLink.get()); + assertEquals(getArtifactRepresentationsLink(artifact.getId()), + representationsLink.get().getHref()); + + final var agreementsLink = result.getLink("agreements"); + assertTrue(agreementsLink.isPresent()); + assertNotNull(agreementsLink.get()); + assertEquals(getArtifactAgreementsLink(artifact.getId()), + agreementsLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private Artifact getArtifact() { + final var desc = new ArtifactDesc(); + desc.setTitle("title"); + desc.setAutomatedDownload(false); + desc.setValue("value"); + desc.setRemoteId(URI.create("https://remote-id.com")); + final var artifact = artifactFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(artifact, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(artifact, "creationDate", date); + ReflectionTestUtils.setField(artifact, "modificationDate", date); + ReflectionTestUtils.setField(artifact, "additional", additional); + + return artifact; + } + + private String getArtifactLink(final UUID artifactId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ArtifactController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + artifactId; + } + + private String getArtifactDataLink(final UUID artifactId) { + return linkTo(methodOn(ResourceControllers.ArtifactController.class) + .getData(artifactId, new QueryInput())).toString(); + } + + private String getArtifactRepresentationsLink(final UUID artifactId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.ArtifactsToRepresentations.class) + .getResource(artifactId, null, null)).toString(); + } + + private String getArtifactAgreementsLink(final UUID artifactId) { + return linkTo(methodOn(RelationControllers.ArtifactsToAgreements.class) + .getResource(artifactId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/CatalogViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/CatalogViewAssemblerTest.java new file mode 100644 index 000000000..4cd9aa4a3 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/CatalogViewAssemblerTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.Catalog; +import io.dataspaceconnector.model.CatalogDesc; +import io.dataspaceconnector.model.CatalogFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +@SpringBootTest(classes = {CatalogViewAssembler.class, ViewAssemblerHelper.class, + CatalogFactory.class}) +public class CatalogViewAssemblerTest { + + @Autowired + private CatalogViewAssembler catalogViewAssembler; + + @Autowired + private CatalogFactory catalogFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.CatalogController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = catalogViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var catalogId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.CatalogController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = catalogViewAssembler.getSelfLink(catalogId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + catalogId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> catalogViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnCatalogView() { + /* ARRANGE */ + final var catalog = getCatalog(); + + /* ACT */ + final var result = catalogViewAssembler.toModel(catalog); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(catalog.getTitle(), result.getTitle()); + Assertions.assertEquals(catalog.getDescription(), result.getDescription()); + Assertions.assertEquals(catalog.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(catalog.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(catalog.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getCatalogLink(catalog.getId()), selfLink.get().getHref()); + + final var offeredResourcesLink = result.getLink("offers"); + assertTrue(offeredResourcesLink.isPresent()); + assertNotNull(offeredResourcesLink.get()); + assertEquals(getCatalogOfferedResourcesLink(catalog.getId()), + offeredResourcesLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private Catalog getCatalog() { + final var desc = new CatalogDesc(); + desc.setTitle("title"); + desc.setDescription("description"); + final var catalog = catalogFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(catalog, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(catalog, "creationDate", date); + ReflectionTestUtils.setField(catalog, "modificationDate", date); + ReflectionTestUtils.setField(catalog, "additional", additional); + + return catalog; + } + + private String getCatalogLink(final UUID catalogId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.CatalogController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + catalogId; + } + + private String getCatalogOfferedResourcesLink(final UUID catalogId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.CatalogsToOfferedResources.class) + .getResource(catalogId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/ContractRuleViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/ContractRuleViewAssemblerTest.java new file mode 100644 index 000000000..163971e4c --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/ContractRuleViewAssemblerTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.ContractRule; +import io.dataspaceconnector.model.ContractRuleDesc; +import io.dataspaceconnector.model.ContractRuleFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +@SpringBootTest(classes = {ContractRuleViewAssembler.class, ViewAssemblerHelper.class, + ContractRuleFactory.class}) +public class ContractRuleViewAssemblerTest { + + @Autowired + private ContractRuleViewAssembler contractRuleViewAssembler; + + @Autowired + private ContractRuleFactory contractRuleFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RuleController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = contractRuleViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var contractRuleId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RuleController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = contractRuleViewAssembler.getSelfLink(contractRuleId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + contractRuleId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> contractRuleViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnContractRuleView() { + /* ARRANGE */ + final var contractRule = getContractRule(); + + /* ACT */ + final var result = contractRuleViewAssembler.toModel(contractRule); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(contractRule.getTitle(), result.getTitle()); + Assertions.assertEquals(contractRule.getValue(), result.getValue()); + Assertions.assertEquals(contractRule.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(contractRule.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(contractRule.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getContractRuleLink(contractRule.getId()), selfLink.get().getHref()); + + final var contractsLink = result.getLink("contracts"); + assertTrue(contractsLink.isPresent()); + assertNotNull(contractsLink.get()); + assertEquals(getContractRuleContractsLink(contractRule.getId()), + contractsLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private ContractRule getContractRule() { + final var desc = new ContractRuleDesc(); + desc.setTitle("title"); + desc.setValue("value"); + final var contractRule = contractRuleFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(contractRule, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(contractRule, "creationDate", date); + ReflectionTestUtils.setField(contractRule, "modificationDate", date); + ReflectionTestUtils.setField(contractRule, "additional", additional); + + return contractRule; + } + + private String getContractRuleLink(final UUID contractRuleId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RuleController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + contractRuleId; + } + + private String getContractRuleContractsLink(final UUID contractRuleId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.RulesToContracts.class) + .getResource(contractRuleId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/ContractViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/ContractViewAssemblerTest.java new file mode 100644 index 000000000..ac7193da1 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/ContractViewAssemblerTest.java @@ -0,0 +1,341 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.Contract; +import io.dataspaceconnector.model.ContractDesc; +import io.dataspaceconnector.model.ContractFactory; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.OfferedResourceFactory; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.RequestedResourceFactory; +import io.dataspaceconnector.model.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +@SpringBootTest(classes = {ContractViewAssembler.class, ViewAssemblerHelper.class, + ContractFactory.class, OfferedResourceFactory.class, RequestedResourceFactory.class}) +public class ContractViewAssemblerTest { + + @Autowired + private ContractViewAssembler contractViewAssembler; + + @Autowired + private ContractFactory contractFactory; + + @Autowired + private OfferedResourceFactory offeredResourceFactory; + + @Autowired + private RequestedResourceFactory requestedResourceFactory; + + final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ContractController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = contractViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var contractId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ContractController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = contractViewAssembler.getSelfLink(contractId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + contractId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> contractViewAssembler.toModel(null)); + } + + @Test + public void toModel_noResources_returnContractViewWithOffersLink() { + /* ARRANGE */ + final var contract = getContract(); + + /* ACT */ + final var result = contractViewAssembler.toModel(contract); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(contract.getTitle(), result.getTitle()); + Assertions.assertEquals(contract.getStart(), result.getStart()); + Assertions.assertEquals(contract.getEnd(), result.getEnd()); + Assertions.assertEquals(contract.getConsumer(), result.getConsumer()); + Assertions.assertEquals(contract.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(contract.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(contract.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getContractLink(contract.getId()), selfLink.get().getHref()); + + final var rulesLink = result.getLink("rules"); + assertTrue(rulesLink.isPresent()); + assertNotNull(rulesLink.get()); + assertEquals(getContractRulesLink(contract.getId()), rulesLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isPresent()); + assertNotNull(offersLink.get()); + assertEquals(getContractOfferedResourcesLink(contract.getId()), + offersLink.get().getHref()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isEmpty()); + } + + @Test + public void toModel_withOfferedResources_returnContractViewWithOffersLink() { + /* ARRANGE */ + final var contract = getContractWithOfferedResources(); + + /* ACT */ + final var result = contractViewAssembler.toModel(contract); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(contract.getTitle(), result.getTitle()); + Assertions.assertEquals(contract.getStart(), result.getStart()); + Assertions.assertEquals(contract.getEnd(), result.getEnd()); + Assertions.assertEquals(contract.getConsumer(), result.getConsumer()); + Assertions.assertEquals(contract.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(contract.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(contract.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getContractLink(contract.getId()), selfLink.get().getHref()); + + final var rulesLink = result.getLink("rules"); + assertTrue(rulesLink.isPresent()); + assertNotNull(rulesLink.get()); + assertEquals(getContractRulesLink(contract.getId()), rulesLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isPresent()); + assertNotNull(offersLink.get()); + assertEquals(getContractOfferedResourcesLink(contract.getId()), + offersLink.get().getHref()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isEmpty()); + } + + @Test + public void toModel_withRequestedResources_returnContractViewWithRequestsLink() { + /* ARRANGE */ + final var contract = getContractWithRequestedResources(); + + /* ACT */ + final var result = contractViewAssembler.toModel(contract); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(contract.getTitle(), result.getTitle()); + Assertions.assertEquals(contract.getStart(), result.getStart()); + Assertions.assertEquals(contract.getEnd(), result.getEnd()); + Assertions.assertEquals(contract.getConsumer(), result.getConsumer()); + Assertions.assertEquals(contract.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(contract.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(contract.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getContractLink(contract.getId()), selfLink.get().getHref()); + + final var rulesLink = result.getLink("rules"); + assertTrue(rulesLink.isPresent()); + assertNotNull(rulesLink.get()); + assertEquals(getContractRulesLink(contract.getId()), rulesLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isEmpty()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isPresent()); + assertNotNull(requestsLink.get()); + assertEquals(getContractRequestedResourcesLink(contract.getId()), + requestsLink.get().getHref()); + } + + @Test + public void toModel_withUnknownResourceType_throwUnreachableLineException() { + /* ARRANGE */ + final var contract = getContractWithUnknownResources(); + + /* ACT && ASSERT */ + assertThrows(UnreachableLineException.class, + () -> contractViewAssembler.toModel(contract)); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private Contract getContract() { + final var desc = new ContractDesc(); + desc.setTitle("title"); + desc.setConsumer(URI.create("https://consumer.com")); + desc.setStart(date); + desc.setEnd(date); + final var contract = contractFactory.create(desc); + + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(contract, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(contract, "creationDate", date); + ReflectionTestUtils.setField(contract, "modificationDate", date); + ReflectionTestUtils.setField(contract, "additional", additional); + + return contract; + } + + private Contract getContractWithOfferedResources() { + final var desc = new OfferedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + final var resource = offeredResourceFactory.create(desc); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + + final var contract = getContract(); + ReflectionTestUtils.setField(contract, "resources", + Collections.singletonList(resource)); + return contract; + } + + private Contract getContractWithRequestedResources() { + final var desc = new RequestedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + final var resource = requestedResourceFactory.create(desc); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + + final var contract = getContract(); + ReflectionTestUtils.setField(contract, "resources", + Collections.singletonList(resource)); + return contract; + } + + private Contract getContractWithUnknownResources() { + final var resource = new UnknownResource(); + + final var contract = getContract(); + ReflectionTestUtils.setField(contract, "resources", + Collections.singletonList(resource)); + return contract; + } + + private String getContractLink(final UUID contractId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ContractController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + contractId; + } + + private String getContractRulesLink(final UUID contractId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.ContractsToRules.class) + .getResource(contractId, null, null)).toString(); + } + + private String getContractOfferedResourcesLink(final UUID contractId) { + return linkTo(methodOn(RelationControllers.ContractsToOfferedResources.class) + .getResource(contractId, null, null)).toString(); + } + + private String getContractRequestedResourcesLink(final UUID contractId) { + return linkTo(methodOn(RelationControllers.ContractsToRequestedResources.class) + .getResource(contractId, null, null)).toString(); + } + + private static class UnknownResource extends Resource { + public UnknownResource() {} + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/OfferedResourceViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/OfferedResourceViewAssemblerTest.java new file mode 100644 index 000000000..38d4be2e1 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/OfferedResourceViewAssemblerTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.OfferedResource; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.OfferedResourceFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +@SpringBootTest(classes = {OfferedResourceViewAssembler.class, ViewAssemblerHelper.class, + OfferedResourceFactory.class}) +public class OfferedResourceViewAssemblerTest { + + @Autowired + private OfferedResourceViewAssembler offeredResourceViewAssembler; + + @Autowired + private OfferedResourceFactory offeredResourceFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.OfferedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = offeredResourceViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var resourceId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.OfferedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = offeredResourceViewAssembler.getSelfLink(resourceId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + resourceId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> offeredResourceViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnOfferedResourceView() { + /* ARRANGE */ + final var offeredResource = getOfferedResource(); + + /* ACT */ + final var result = offeredResourceViewAssembler.toModel(offeredResource); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(offeredResource.getTitle(), result.getTitle()); + Assertions.assertEquals(offeredResource.getDescription(), result.getDescription()); + Assertions.assertEquals(offeredResource.getKeywords(), result.getKeywords()); + Assertions.assertEquals(offeredResource.getPublisher(), result.getPublisher()); + Assertions.assertEquals(offeredResource.getLanguage(), result.getLanguage()); + Assertions.assertEquals(offeredResource.getLicence(), result.getLicence()); + Assertions.assertEquals(offeredResource.getVersion(), result.getVersion()); + Assertions.assertEquals(offeredResource.getSovereign(), result.getSovereign()); + Assertions.assertEquals(offeredResource.getEndpointDocumentation(), result.getEndpointDocumentation()); + Assertions.assertEquals(offeredResource.getAdditional(), result.getAdditional()); + Assertions.assertEquals(offeredResource.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(offeredResource.getModificationDate(), result.getModificationDate()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getOfferedResourceLink(offeredResource.getId()), selfLink.get().getHref()); + + final var contractsLink = result.getLink("contracts"); + assertTrue(contractsLink.isPresent()); + assertNotNull(contractsLink.get()); + assertEquals(getOfferedResourceContractsLink(offeredResource.getId()), + contractsLink.get().getHref()); + + final var representationsLink = result.getLink("representations"); + assertTrue(representationsLink.isPresent()); + assertNotNull(representationsLink.get()); + assertEquals(getOfferedResourceRepresentationsLink(offeredResource.getId()), + representationsLink.get().getHref()); + + final var catalogsLink = result.getLink("catalogs"); + assertTrue(catalogsLink.isPresent()); + assertNotNull(catalogsLink.get()); + assertEquals(getOfferedResourceCatalogsLink(offeredResource.getId()), + catalogsLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private OfferedResource getOfferedResource() { + final var desc = new OfferedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + final var resource = offeredResourceFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + ReflectionTestUtils.setField(resource, "additional", additional); + + return resource; + } + + private String getOfferedResourceLink(final UUID resourceId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.OfferedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + resourceId; + } + + private String getOfferedResourceContractsLink(final UUID resourceId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.OfferedResourcesToContracts.class) + .getResource(resourceId, null, null)).toString(); + } + + private String getOfferedResourceRepresentationsLink(final UUID resourceId) { + return linkTo(methodOn(RelationControllers.OfferedResourcesToRepresentations.class) + .getResource(resourceId, null, null)).toString(); + } + + private String getOfferedResourceCatalogsLink(final UUID resourceId) { + return linkTo(methodOn(RelationControllers.OfferedResourcesToCatalogs.class) + .getResource(resourceId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/RepresentationViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/RepresentationViewAssemblerTest.java new file mode 100644 index 000000000..3557be90b --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/RepresentationViewAssemblerTest.java @@ -0,0 +1,344 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.exceptions.UnreachableLineException; +import io.dataspaceconnector.model.OfferedResourceDesc; +import io.dataspaceconnector.model.OfferedResourceFactory; +import io.dataspaceconnector.model.Representation; +import io.dataspaceconnector.model.RepresentationDesc; +import io.dataspaceconnector.model.RepresentationFactory; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.RequestedResourceFactory; +import io.dataspaceconnector.model.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +@SpringBootTest(classes = {RepresentationViewAssembler.class, ViewAssemblerHelper.class, + RepresentationFactory.class, OfferedResourceFactory.class, RequestedResourceFactory.class}) +public class RepresentationViewAssemblerTest { + + @Autowired + private RepresentationViewAssembler representationViewAssembler; + + @Autowired + private RepresentationFactory representationFactory; + + @Autowired + private OfferedResourceFactory offeredResourceFactory; + + @Autowired + private RequestedResourceFactory requestedResourceFactory; + + final ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RepresentationController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = representationViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var representationId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RepresentationController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = representationViewAssembler.getSelfLink(representationId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + representationId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> representationViewAssembler.toModel(null)); + } + + @Test + public void toModel_noResources_returnRepresentationViewWithOffersLink() { + /* ARRANGE */ + final var representation = getRepresentation(); + + /* ACT */ + final var result = representationViewAssembler.toModel(representation); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(representation.getTitle(), result.getTitle()); + Assertions.assertEquals(representation.getMediaType(), result.getMediaType()); + Assertions.assertEquals(representation.getLanguage(), result.getLanguage()); + Assertions.assertEquals(representation.getRemoteId(), result.getRemoteId()); + Assertions.assertEquals(representation.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(representation.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(representation.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getRepresentationLink(representation.getId()), selfLink.get().getHref()); + + final var artifactsLink = result.getLink("artifacts"); + assertTrue(artifactsLink.isPresent()); + assertNotNull(artifactsLink.get()); + assertEquals(getRepresentationArtifactsLink(representation.getId()), + artifactsLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isPresent()); + assertNotNull(offersLink.get()); + assertEquals(getRepresentationOfferedResourcesLink(representation.getId()), + offersLink.get().getHref()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isEmpty()); + } + + @Test + public void toModel_withOfferedResources_returnRepresentationViewWithOffersLink() { + /* ARRANGE */ + final var representation = getRepresentationWithOfferedResources(); + + /* ACT */ + final var result = representationViewAssembler.toModel(representation); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(representation.getTitle(), result.getTitle()); + Assertions.assertEquals(representation.getMediaType(), result.getMediaType()); + Assertions.assertEquals(representation.getLanguage(), result.getLanguage()); + Assertions.assertEquals(representation.getRemoteId(), result.getRemoteId()); + Assertions.assertEquals(representation.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(representation.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(representation.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getRepresentationLink(representation.getId()), selfLink.get().getHref()); + + final var artifactsLink = result.getLink("artifacts"); + assertTrue(artifactsLink.isPresent()); + assertNotNull(artifactsLink.get()); + assertEquals(getRepresentationArtifactsLink(representation.getId()), + artifactsLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isPresent()); + assertNotNull(offersLink.get()); + assertEquals(getRepresentationOfferedResourcesLink(representation.getId()), + offersLink.get().getHref()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isEmpty()); + } + + @Test + public void toModel_withRequestedResources_returnRepresentationViewWithRequestsLink() { + /* ARRANGE */ + final var representation = getRepresentationWithRequestedResources(); + + /* ACT */ + final var result = representationViewAssembler.toModel(representation); + + /* ASSERT */ + assertNotNull(result); + Assertions.assertEquals(representation.getTitle(), result.getTitle()); + Assertions.assertEquals(representation.getMediaType(), result.getMediaType()); + Assertions.assertEquals(representation.getLanguage(), result.getLanguage()); + Assertions.assertEquals(representation.getRemoteId(), result.getRemoteId()); + Assertions.assertEquals(representation.getCreationDate(), result.getCreationDate()); + Assertions.assertEquals(representation.getModificationDate(), result.getModificationDate()); + Assertions.assertEquals(representation.getAdditional(), result.getAdditional()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getRepresentationLink(representation.getId()), selfLink.get().getHref()); + + final var artifactsLink = result.getLink("artifacts"); + assertTrue(artifactsLink.isPresent()); + assertNotNull(artifactsLink.get()); + assertEquals(getRepresentationArtifactsLink(representation.getId()), + artifactsLink.get().getHref()); + + final var offersLink = result.getLink("offers"); + assertTrue(offersLink.isEmpty()); + + final var requestsLink = result.getLink("requests"); + assertTrue(requestsLink.isPresent()); + assertNotNull(requestsLink.get()); + assertEquals(getRepresentationRequestedResourcesLink(representation.getId()), + requestsLink.get().getHref()); + } + + @Test + public void toModel_withUnknownResourceType_throwUnreachableLineException() { + /* ARRANGE */ + final var representation = getRepresentationWithUnknownResources(); + + /* ACT && ASSERT */ + assertThrows(UnreachableLineException.class, + () -> representationViewAssembler.toModel(representation)); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private Representation getRepresentation() { + final var desc = new RepresentationDesc(); + desc.setTitle("title"); + desc.setMediaType("application/json"); + desc.setLanguage("EN"); + desc.setRemoteId(URI.create("https://remote-id.com")); + final var representation = representationFactory.create(desc); + + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(representation, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(representation, "creationDate", date); + ReflectionTestUtils.setField(representation, "modificationDate", date); + ReflectionTestUtils.setField(representation, "additional", additional); + + return representation; + } + + private Representation getRepresentationWithOfferedResources() { + final var desc = new OfferedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + final var resource = offeredResourceFactory.create(desc); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + + final var representation = getRepresentation(); + ReflectionTestUtils.setField(representation, "resources", + Collections.singletonList(resource)); + return representation; + } + + private Representation getRepresentationWithRequestedResources() { + final var desc = new RequestedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + final var resource = requestedResourceFactory.create(desc); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + + final var representation = getRepresentation(); + ReflectionTestUtils.setField(representation, "resources", + Collections.singletonList(resource)); + return representation; + } + + private Representation getRepresentationWithUnknownResources() { + final var resource = new UnknownResource(); + + final var representation = getRepresentation(); + ReflectionTestUtils.setField(representation, "resources", + Collections.singletonList(resource)); + return representation; + } + + private String getRepresentationLink(final UUID representationId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RepresentationController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + representationId; + } + + private String getRepresentationArtifactsLink(final UUID representationId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.RepresentationsToArtifacts.class) + .getResource(representationId, null, null)).toString(); + } + + private String getRepresentationOfferedResourcesLink(final UUID representationId) { + return linkTo(methodOn(RelationControllers.RepresentationsToOfferedResources.class) + .getResource(representationId, null, null)).toString(); + } + + private String getRepresentationRequestedResourcesLink(final UUID representationId) { + return linkTo(methodOn(RelationControllers.RepresentationsToRequestedResources.class) + .getResource(representationId, null, null)).toString(); + } + + private static class UnknownResource extends Resource { + public UnknownResource() {} + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/RequestedResourceViewAssemblerTest.java b/src/test/java/io/dataspaceconnector/view/RequestedResourceViewAssemblerTest.java new file mode 100644 index 000000000..b3d619ad2 --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/RequestedResourceViewAssemblerTest.java @@ -0,0 +1,200 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.net.URI; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.RelationControllers; +import io.dataspaceconnector.controller.resources.ResourceControllers; +import io.dataspaceconnector.model.RequestedResource; +import io.dataspaceconnector.model.RequestedResourceDesc; +import io.dataspaceconnector.model.RequestedResourceFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.methodOn; + +@SpringBootTest(classes = {RequestedResourceViewAssembler.class, ViewAssemblerHelper.class, + RequestedResourceFactory.class}) +public class RequestedResourceViewAssemblerTest { + + @Autowired + private RequestedResourceViewAssembler requestedResourceViewAssembler; + + @Autowired + private RequestedResourceFactory requestedResourceFactory; + + @Test + public void getSelfLink_inputNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RequestedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = requestedResourceViewAssembler.getSelfLink(null); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_validInput_returnSelfLink() { + /* ARRANGE */ + final var resourceId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RequestedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = requestedResourceViewAssembler.getSelfLink(resourceId); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + resourceId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void toModel_inputNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> requestedResourceViewAssembler.toModel(null)); + } + + @Test + public void toModel_validInput_returnRequestedResourceView() { + /* ARRANGE */ + final var requestedResource = getRequestedResource(); + + /* ACT */ + final var result = requestedResourceViewAssembler.toModel(requestedResource); + + /* ASSERT */ + assertNotNull(result); + assertEquals(requestedResource.getTitle(), result.getTitle()); + assertEquals(requestedResource.getDescription(), result.getDescription()); + assertEquals(requestedResource.getKeywords(), result.getKeywords()); + assertEquals(requestedResource.getPublisher(), result.getPublisher()); + assertEquals(requestedResource.getLanguage(), result.getLanguage()); + assertEquals(requestedResource.getLicence(), result.getLicence()); + assertEquals(requestedResource.getVersion(), result.getVersion()); + assertEquals(requestedResource.getSovereign(), result.getSovereign()); + assertEquals(requestedResource.getEndpointDocumentation(), + result.getEndpointDocumentation()); + assertEquals(requestedResource.getRemoteId(), result.getRemoteId()); + assertEquals(requestedResource.getAdditional(), result.getAdditional()); + assertEquals(requestedResource.getCreationDate(), result.getCreationDate()); + assertEquals(requestedResource.getModificationDate(), result.getModificationDate()); + + final var selfLink = result.getLink("self"); + assertTrue(selfLink.isPresent()); + assertNotNull(selfLink.get()); + assertEquals(getRequestedResourceLink(requestedResource.getId()), selfLink.get().getHref()); + + final var contractsLink = result.getLink("contracts"); + assertTrue(contractsLink.isPresent()); + assertNotNull(contractsLink.get()); + assertEquals(getRequestedResourceContractsLink(requestedResource.getId()), + contractsLink.get().getHref()); + + final var representationsLink = result.getLink("representations"); + assertTrue(representationsLink.isPresent()); + assertNotNull(representationsLink.get()); + assertEquals(getRequestedResourceRepresentationsLink(requestedResource.getId()), + representationsLink.get().getHref()); + + final var catalogsLink = result.getLink("catalogs"); + assertTrue(catalogsLink.isPresent()); + assertNotNull(catalogsLink.get()); + assertEquals(getRequestedResourceCatalogsLink(requestedResource.getId()), + catalogsLink.get().getHref()); + } + + /************************************************************************** + * Utilities. + *************************************************************************/ + + private RequestedResource getRequestedResource() { + final var desc = new RequestedResourceDesc(); + desc.setLanguage("EN"); + desc.setTitle("title"); + desc.setDescription("description"); + desc.setKeywords(Collections.singletonList("keyword")); + desc.setEndpointDocumentation(URI.create("https://endpointDocumentation.com")); + desc.setLicence(URI.create("https://license.com")); + desc.setPublisher(URI.create("https://publisher.com")); + desc.setSovereign(URI.create("https://sovereign.com")); + desc.setRemoteId(URI.create("https://remote-id.com")); + final var resource = requestedResourceFactory.create(desc); + + final var date = ZonedDateTime.now(ZoneOffset.UTC); + final var additional = new HashMap(); + additional.put("key", "value"); + + ReflectionTestUtils.setField(resource, "id", UUID.randomUUID()); + ReflectionTestUtils.setField(resource, "creationDate", date); + ReflectionTestUtils.setField(resource, "modificationDate", date); + ReflectionTestUtils.setField(resource, "additional", additional); + + return resource; + } + + private String getRequestedResourceLink(final UUID resourceId) { + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.RequestedResourceController.class + .getAnnotation(RequestMapping.class).value()[0]; + return baseUrl + path + "/" + resourceId; + } + + private String getRequestedResourceContractsLink(final UUID resourceId) { + return WebMvcLinkBuilder.linkTo(methodOn(RelationControllers.RequestedResourcesToContracts.class) + .getResource(resourceId, null, null)).toString(); + } + + private String getRequestedResourceRepresentationsLink(final UUID resourceId) { + return linkTo(methodOn(RelationControllers.RequestedResourcesToRepresentations.class) + .getResource(resourceId, null, null)).toString(); + } + + private String getRequestedResourceCatalogsLink(final UUID resourceId) { + return linkTo(methodOn(RelationControllers.RequestedResourcesToCatalogs.class) + .getResource(resourceId, null, null)).toString(); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/ViewAssemblerHelperTest.java b/src/test/java/io/dataspaceconnector/view/ViewAssemblerHelperTest.java new file mode 100644 index 000000000..d6e199d8b --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/ViewAssemblerHelperTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import java.util.UUID; + +import io.dataspaceconnector.controller.resources.ResourceControllers; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(classes = {ViewAssemblerHelper.class}) +public class ViewAssemblerHelperTest { + + @Test + public void getSelfLink_bothParametersNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> ViewAssemblerHelper.getSelfLink(null, null)); + } + + @Test + public void getSelfLink_controllerClassNull_throwIllegalArgumentException() { + /* ACT && ASSERT */ + assertThrows(IllegalArgumentException.class, + () -> ViewAssemblerHelper.getSelfLink(UUID.randomUUID(), null)); + } + + @Test + public void getSelfLink_entityIdNull_returnBasePathWithoutId() { + /* ARRANGE */ + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ArtifactController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = ViewAssemblerHelper.getSelfLink(null, + ResourceControllers.ArtifactController.class); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + + @Test + public void getSelfLink_inputCorrect_returnSelfLink() { + /* ARRANGE */ + final var resourceId = UUID.randomUUID(); + final var baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath() + .toUriString(); + final var path = ResourceControllers.ArtifactController.class + .getAnnotation(RequestMapping.class).value()[0]; + final var rel = "self"; + + /* ACT */ + final var result = ViewAssemblerHelper.getSelfLink(resourceId, + ResourceControllers.ArtifactController.class); + + /* ASSERT */ + assertNotNull(result); + assertEquals(baseUrl + path + "/" + resourceId, result.getHref()); + assertEquals(rel, result.getRel().value()); + } + +} diff --git a/src/test/java/io/dataspaceconnector/view/ViewEqualsTests.java b/src/test/java/io/dataspaceconnector/view/ViewEqualsTests.java new file mode 100644 index 000000000..f469f777f --- /dev/null +++ b/src/test/java/io/dataspaceconnector/view/ViewEqualsTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Fraunhofer Institute for Software and Systems Engineering + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dataspaceconnector.view; + +import io.dataspaceconnector.controller.ExampleController; +import io.dataspaceconnector.controller.MainController; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import org.springframework.hateoas.Link; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +public class ViewEqualsTests { + + private final Link link1 = linkTo(MainController.class).withSelfRel(); + private final Link link2 = linkTo(ExampleController.class).withSelfRel(); + + @Test + public void verifyEquals_agreementView_passesVerification() { + EqualsVerifier.simple() + .forClass(AgreementView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_artifactView_passesVerification() { + EqualsVerifier.simple() + .forClass(ArtifactView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_catalogView_passesVerification() { + EqualsVerifier.simple() + .forClass(CatalogView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_contractRuleView_passesVerification() { + EqualsVerifier.simple() + .forClass(ContractRuleView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_contractView_passesVerification() { + EqualsVerifier.simple() + .forClass(ContractView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_offeredResourceView_passesVerification() { + EqualsVerifier.simple() + .forClass(OfferedResourceView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_representationView_passesVerification() { + EqualsVerifier.simple() + .forClass(RepresentationView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + + @Test + public void verifyEquals_requestedResourceView_passesVerification() { + EqualsVerifier.simple() + .forClass(RequestedResourceView.class) + .withPrefabValues(Link.class, link1 ,link2) + .withNonnullFields("links") + .verify(); + } + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 83e0e7d6c..e3faf9788 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,29 +1,73 @@ -######################################################################################################################## -## Dataspace Connector ## -######################################################################################################################## +#################################################################################################### +## Dataspace Connector ## +#################################################################################################### ## Spring Tomcat server.port=8080 +## General Information +spring.application.name=Dataspace-Connector +spring.banner.location=classpath:banner.txt + +title=@project.name@ +version=@project.version@ +project_desc=@project.description@ +organization_name=@project.organization.name@ +contact_url=@project.url@ +contact_email=@email@ +licence=@licence_name@ +licence_url=@licence_url@ + +## Spring deserialization +spring.jackson.deserialization.fail-on-unknown-properties=true + ## Spring Security spring.security.user.name=admin spring.security.user.password=password ## OpenAPI -springdoc.swagger-ui.path=/admin/api -springdoc.swagger-ui.operationsSorter=method +springdoc.swagger-ui.path=/api/docs +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.disable-swagger-default-url=true -## TLS -server.ssl.enabled=true -server.ssl.key-store-policyType=PKCS12 -server.ssl.key-store=classpath:conf/ssl-localhost.p12 -server.ssl.key-store-password=password -server.ssl.key-alias=1 -#security.require-ssl=true +## Endpoints +management.endpoints.enabled-by-default=false +#management.endpoints.web.exposure.include=logfile, loggers +#management.endpoint.loggers.enabled=true +#management.endpoint.logfile.enabled=true +#management.endpoint.logfile.external-file=./log/dataspaceconnector.log + +## Jaeger +opentracing.jaeger.udp-sender.host=localhost +opentracing.jaeger.udp-sender.port=6831 +opentracing.jaeger.log-spans=true + +#################################################################################################### +## IDS Properties ## +#################################################################################################### + +## Configuration Properties +configuration.path=conf/config.json +configuration.keyStorePassword=password +configuration.keyAlias=1 +configuration.trustStorePassword=password + +## DAPS +daps.token.url=https://daps.aisec.fraunhofer.de +daps.key.url=https://daps.aisec.fraunhofer.de/v2/.well-known/jwks.json -######################################################################################################################## -## Storage ## -######################################################################################################################## +## Clearing House +clearing.house.url=https://ch-ids.aisec.fraunhofer.de/logs/messages/ + +## Connector Settings +policy.negotiation=true +policy.allow-unsupported-patterns=false +policy.framework=INTERNAL +# policy.framework=MYDATA + +#################################################################################################### +## Storage ## +#################################################################################################### ### H2 Database spring.datasource.url=jdbc:h2:file:./target/db/resources @@ -32,19 +76,32 @@ spring.datasource.username=sa spring.datasource.password=password ## Enable H2 Console Access -spring.h2.console.enabled=true -spring.h2.console.path=/admin/h2 +spring.h2.console.enabled=false +spring.h2.console.path=/database spring.h2.console.settings.web-allow-others=true ## Import Data #spring.datasource.data=classpath:/data/data.sql ### Hibernate Properties -# spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update + +## Disable open in view transactions +spring.jpa.open-in-view=true + +#################################################################################################### +## HTTP/S ## +#################################################################################################### + +# server.http2.enabled=true -# Hibernate Logging -logging.level.org.hibernate.SQL= DEBUG +## TLS +server.ssl.enabled=true +server.ssl.key-store-type=PKCS12 +server.ssl.key-store=classpath:conf/keystore-localhost.p12 +server.ssl.key-store-password=password +server.ssl.key-alias=1 +#security.require-ssl=true ## MULTIPART (MultipartProperties) spring.servlet.multipart.enabled=true @@ -52,16 +109,10 @@ spring.servlet.multipart.file-size-threshold=2KB spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-request-size=215MB -######################################################################################################################## -## IDS Properties ## -######################################################################################################################## +## Timeout settings (millis) +http.timeout.connect=10000 +http.timeout.read=10000 +http.timeout.write=10000 +http.timeout.call=10000 -## Configuration Properties -configuration.path=conf/config.json -configuration.keyStorePassword=password -configuration.keyAlias=1 -configuration.trustStorePassword=password - -## DAPS -daps.token.url=https://daps.aisec.fraunhofer.de -daps.key.url=https://daps.aisec.fraunhofer.de/.well-known/jwks.json +httptrace.enabled=false diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..1f0955d45 --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline