diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..73f87050f --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,34 @@ +# 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: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + build: + 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..7981a6496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,46 @@ # Changelog All notable changes to this project will be documented in this file. +## [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 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0517fa32d --- /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..33cc38052 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/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..5562a5937 100644 --- a/README.md +++ b/README.md @@ -1,269 +1,100 @@ -# 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.** +

+
+ Dataspace Connector Logo +
+ Dataspace Connector +
+

+ + +

+ Contact • + Issues • + Contribute • + 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 Framework](https://github.com/FraunhoferISST/IDS-Connector-Framework) +for IDS functionalities and message handling. It provides a REST API for loading, updating, and +deleting resources with data and its metadata, persisted in a local database. Next to the internal +database, external REST endpoints may be connected as data sources. The Dataspace Connector +supports IDS conform message handling with other IDS connectors and IDS brokers and implements +usage control for eight IDS usage policy patterns. ## 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) +- [Wiki](https://github.com/FraunhoferISST/DataspaceConnector/wiki) + - [Database Configuration](https://github.com/FraunhoferISST/DataspaceConnector/wiki/database-configuration) + - [Development](https://github.com/FraunhoferISST/DataspaceConnector/wiki/development) + - [Examples](https://github.com/FraunhoferISST/DataspaceConnector/wiki/examples) + - [Frequently Asked Questions](https://github.com/FraunhoferISST/DataspaceConnector/wiki/faq) + - [Getting started](https://github.com/FraunhoferISST/DataspaceConnector/wiki/getting-started) + - [IDS Communication Guide](https://github.com/FraunhoferISST/DataspaceConnector/wiki/ids-communication-guide) + - [IDS Connector Architecture](https://github.com/FraunhoferISST/DataspaceConnector/wiki/ids-connector-architecture) + - [Logging](https://github.com/FraunhoferISST/DataspaceConnector/wiki/logging) + - [Roadmap](https://github.com/FraunhoferISST/DataspaceConnector/wiki/roadmap) + - [Software Documentation](https://github.com/FraunhoferISST/DataspaceConnector/wiki/software-documentation) + - [Software Tests](https://github.com/FraunhoferISST/DataspaceConnector/wiki/software-tests) + - [Usage Control](https://github.com/FraunhoferISST/DataspaceConnector/wiki/usage-control) +- [Quick Start](#quick-start) +- [IDS Components](#ids-components) +- [Contributing](#contributing) +- [Developers](#developers) - [License](#license) -## Features +A project overview and short descriptions of each wiki section are presented +[here](https://github.com/FraunhoferISST/DataspaceConnector/wiki). + +## Quick Start -This is a list of currently implemented features, which is continuously updated. +If you want to build and run locally, ensure that at least Java 11 is installed. Then, follow these steps: -* 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 +1. Clone this repository. +2. Execute `cd dataspace-connector` and `mvnw clean package`. +3. Navigate to `/target` and run `java -jar dataspace-connector-{VERSION}.jar`. +4. If everything worked fine, the connector is available at https://localhost:8080/. The API can +be accessed at https://localhost:8080/admin/api. -### Technologies +For more details, see [here](https://github.com/FraunhoferISST/DataspaceConnector/wiki/development). +If you do not want to deploy the application yourself, have a look at +[how to use the test setups](https://github.com/FraunhoferISST/DataspaceConnector/wiki/getting-started). -`Java`, `Maven`, `Spring Boot`, `Rest`, `OpenAPI`, `Swagger`, `SLF4J`, `Docker`, `JSON(-LD)` +## IDS Components -### IDS Components +The [ConfigManager](https://github.com/FraunhoferISST/IDS-ConfigurationManager) and its +[GUI](https://github.com/fkie/ids-configmanager-ui) aim to facilitate the configuration of the +Dataspace Connector and further IDS connector implementations. Both projects are also open source. -| Library/Component | Version | License | Owner | Contact | -| ------ | ------ | ------ | ------ | ------ | +| 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) | +| [IDS Framework](https://github.com/FraunhoferISST/IDS-Connector-Framework) | 4.0.1 | Apache 2.0 | Fraunhofer ISST | [Tim Berthold](mailto:tim.berthold@isst.fraunhofer.de) | +| [IDS Broker](https://broker.ids.isst.fraunhofer.de/) | 4.0.0 | open core | 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) | +## Contributing -## 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`. +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). -**Database** +## Developers -The data resources are persisted in an H2 database. +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). -* Local datasource: `/target/db/resources` -* Console path: https://localhost:8080/admin/h2 +The core development is driven by +* [Heinrich Pettenpohl](https://github.com/HeinrichPet), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [Julia Pampus](https://github.com/juliapampus), [Fraunhofer ISST](https://www.isst.fraunhofer.de/en.html) +* [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): +* ... ## License Copyright © 2020 Fraunhofer ISST. This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) for details. diff --git a/docker-compose.yml b/docker-compose.yml index 46074f643..ce67d4f9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,9 @@ services: - ./postgres.env networks: - connector + volumes: + - connector-data:/var/lib/postgresql/data + connector: build: context: . @@ -25,3 +28,6 @@ services: networks: connector: + +volumes: + connector-data: {} \ No newline at end of file diff --git a/images/.gitkeep b/images/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/images/architecture.png b/images/architecture.png deleted file mode 100644 index 0e0097b68..000000000 Binary files a/images/architecture.png and /dev/null differ 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/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/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/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..f49ee4f90 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,15 +1,15 @@ 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 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: 4.0.0 servers: - url: https://localhost:8080 description: Generated server url @@ -22,15 +22,17 @@ tags: description: Endpoints for invoking external connector requests - name: 'Connector: Selfservice' description: Endpoints for connector information + - name: Examples + description: Endpoints for testing purpose - name: 'Connector: IDS Broker Communication' description: Endpoints for invoking broker communication paths: - /admin/api/broker/resource/{resource-id}/update: + /admin/api/broker/update/{resource-id}: post: tags: - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. + summary: Update Resource at Broker + description: Update an IDS resource at an IDS broker. operationId: updateResourceAtBroker parameters: - name: broker @@ -48,18 +50,36 @@ paths: type: string format: uuid responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/update/{resource-id}/remove: + type: string + /admin/api/broker/remove/{resource-id}: post: tags: - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. + summary: Remove Resource from Broker + description: Remove an IDS resource at an IDS broker. operationId: deleteResourceAtBroker parameters: - name: broker @@ -77,19 +97,37 @@ paths: type: string format: uuid responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/register: + type: string + /admin/api/broker/unregister: post: tags: - 'Connector: IDS Broker Communication' - summary: Register Connector - description: Register or update connector at an IDS broker. - operationId: updateAtBroker + summary: Unregister Connector + description: Unregister connector at an IDS broker. + operationId: unregisterAtBroker parameters: - name: broker in: query @@ -99,19 +137,31 @@ paths: type: string example: https://broker.ids.isst.fraunhofer.de/infrastructure responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/update: + type: string + /admin/api/broker/query: post: tags: - 'Connector: IDS Broker Communication' - summary: Register Connector - description: Register or update connector at an IDS broker. - operationId: updateAtBroker_1 + summary: Broker Query Request + description: Send a query request to an IDS broker. + operationId: queryBroker parameters: - name: broker in: query @@ -120,20 +170,45 @@ paths: schema: type: string example: https://broker.ids.isst.fraunhofer.de/infrastructure + requestBody: + content: + application/json: + schema: + type: string + description: Database query (SparQL) + example: |- + SELECT ?subject ?predicate ?object + FROM + WHERE { + ?subject ?predicate ?object + }; + required: true responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/unregister: + type: string + /admin/api/broker/register: post: tags: - 'Connector: IDS Broker Communication' - summary: Unregister Connector - description: Unregister connector at an IDS broker. - operationId: unregisterAtBroker + summary: Register Connector + description: Register or update connector at an IDS broker. + operationId: updateAtBroker parameters: - name: broker in: query @@ -143,19 +218,31 @@ paths: type: string example: https://broker.ids.isst.fraunhofer.de/infrastructure responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/broker/query: + type: string + /admin/api/broker/update: post: tags: - 'Connector: IDS Broker Communication' - summary: Broker Query Request - description: Send a query request to an IDS broker. - operationId: queryBroker + summary: Register Connector + description: Register or update connector at an IDS broker. + operationId: updateAtBroker_1 parameters: - name: broker in: query @@ -165,30 +252,42 @@ paths: type: string example: https://broker.ids.isst.fraunhofer.de/infrastructure responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/example/configuration: get: tags: - - 'Connector: Selfservice' - summary: Get Connector configuration - description: Get the connector's configuration. - operationId: getConnector + - Examples + summary: Get Sample Connector configuration + description: Get a sample connector configuration for the config.json. + operationId: getConnectorConfiguration responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/example/usage-policy: post: tags: - - 'Connector: Selfservice' + - Examples summary: Get example policy description: Get an example policy for a given policy pattern. operationId: getExampleUsagePolicy @@ -210,46 +309,194 @@ paths: - USAGE_NOTIFICATION responses: "200": - description: OK + description: Ok content: '*/*': schema: type: object - /admin/api/example/policy-pattern: + /admin/api/example/policy-validation: post: tags: - - 'Connector: Selfservice' + - Examples 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 + /admin/api/negotiation: + get: + tags: + - 'Connector: Selfservice' + summary: Endpoint for Policy Negotiation Status Check + description: Return the policy negotiation status. + operationId: getNegotiationStatus + responses: + "200": + description: Ok + content: + '*/*': + schema: + type: string + put: + tags: + - 'Connector: Selfservice' + summary: Endpoint for Policy Negotiation Status + description: Turn the policy negotiation on or off. + operationId: setNegotiationStatus parameters: - - name: policy + - name: status in: query - description: The JSON string representing a policy required: true schema: - type: string + type: boolean responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/selfservice: + type: string + /: + get: + tags: + - 'Connector: Selfservice' + summary: Public Endpoint for Connector Self-description + description: Get the connector's reduced self-description. + operationId: getPublicSelfDescription_1 + responses: + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "200": + description: Ok + content: + '*/*': + schema: + type: string + /admin/api/connector: get: tags: - 'Connector: Selfservice' summary: Connector Self-description description: Get the connector's self-description. operationId: getSelfService + responses: + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "200": + description: Ok + content: + '*/*': + schema: + type: string + /admin/api/ignore-unsupported-patterns: + get: + tags: + - 'Connector: Selfservice' + summary: Endpoint for Pattern Checking + description: Return if unsupported patterns are ignored when requesting data. + operationId: getPatternStatus responses: "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string + put: + tags: + - 'Connector: Selfservice' + summary: Endpoint for Allowing Unsupported Patterns + description: Allow requesting data without policy enforcement if an unsupported + pattern is recognized. + operationId: getPatternStatus_1 + parameters: + - name: status + in: query + required: true + schema: + type: boolean + responses: + "200": + description: Ok + content: + '*/*': + schema: + type: string + /admin/api/request/contract: + post: + tags: + - 'Connector: IDS Connector Communication' + summary: Contract Request + description: Send a contract request to another IDS connector. + operationId: requestContract + 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 + requestBody: + content: + application/json: + schema: + type: string + description: The contract offer for the requested resource. + responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "200": + description: Ok + content: + '*/*': + schema: + type: string /admin/api/request/artifact: post: tags: @@ -276,6 +523,14 @@ paths: type: string format: uri example: https://w3id.org/idsa/autogen/artifact/a4212311-86e4-40b3-ace3-ef29cd687cf9 + - name: transferContract + in: query + description: The URI of the contract agreement. + required: false + schema: + type: string + format: uri + example: https://w3id.org/idsa/autogen/contractAgreement/a4212311-86e4-40b3-ace3-ef29cd687cf9 - name: key in: query description: A unique validation key. @@ -284,12 +539,30 @@ paths: type: string format: uuid responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "403": + description: Forbidden + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/request/description: post: tags: @@ -306,7 +579,7 @@ paths: type: string format: uri example: https://localhost:8080/api/ids/data - - name: requestedArtifact + - name: requestedResource in: query description: The URI of the requested resource. required: false @@ -315,12 +588,24 @@ paths: format: uri example: https://w3id.org/idsa/autogen/resource/a4212311-86e4-40b3-ace3-ef29cd687cf9 responses: + "401": + description: Unauthorized + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/resources/{resource-id}: get: tags: @@ -337,8 +622,20 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object "200": - description: OK + description: Ok content: '*/*': schema: @@ -364,12 +661,30 @@ paths: $ref: '#/components/schemas/ResourceMetadata' required: true responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "400": + description: Invalid resource + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string delete: tags: - 'Connector: Resource Handling' @@ -385,12 +700,18 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/resources/{resource-id}/access: get: tags: @@ -407,8 +728,20 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object "200": - description: OK + description: Ok content: '*/*': schema: @@ -436,8 +769,20 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: object + "500": + description: Internal server error + content: + '*/*': + schema: + type: object "200": - description: OK + description: Ok content: '*/*': schema: @@ -470,12 +815,30 @@ paths: $ref: '#/components/schemas/ResourceRepresentation' required: true responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string + "400": + description: Invalid representation + content: + '*/*': + schema: + type: string delete: tags: - 'Connector: Resource Handling' @@ -498,20 +861,39 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/resource: + type: string + /admin/api/resources/{resource-id}/representation: post: tags: - 'Connector: Resource Handling' - summary: Register Resource - description: Register a resource by its metadata. - operationId: createResource + summary: Add Representation + description: Add a representation to a resource. + operationId: addRepresentation parameters: + - name: resource-id + in: path + description: The resource uuid. + required: true + schema: + type: string + format: uuid - name: id in: query required: false @@ -522,15 +904,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceMetadata' + $ref: '#/components/schemas/ResourceRepresentation' required: true responses: - "200": - description: OK + "404": + description: Not found content: '*/*': schema: - type: object + type: string + "409": + description: Representation already exists + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "201": + description: Representation created + content: + '*/*': + schema: + type: string + "400": + description: Invalid representation + content: + '*/*': + schema: + type: string /admin/api/resources/{resource-id}/contract: get: tags: @@ -547,12 +953,24 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string put: tags: - 'Connector: Resource Handling' @@ -575,27 +993,38 @@ paths: description: A new resource contract. required: true responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "400": + description: Invalid resource + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object - /admin/api/resources/{resource-id}/representation: + type: string + /admin/api/resources/resource: post: tags: - 'Connector: Resource Handling' - summary: Add Representation - description: Add a representation to a resource. - operationId: addRepresentation + summary: Register Resource + description: Register a resource by its metadata. + operationId: createResource parameters: - - name: resource-id - in: path - description: The resource uuid. - required: true - schema: - type: string - format: uuid - name: id in: query required: false @@ -606,15 +1035,33 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceRepresentation' + $ref: '#/components/schemas/ResourceMetadata' required: true responses: - "200": - description: OK + "500": + description: Internal server error content: '*/*': schema: - type: object + type: string + "400": + description: Invalid resource + content: + '*/*': + schema: + type: string + "201": + description: Resource created + content: + '*/*': + schema: + type: string + "409": + description: Resource already exists + content: + '*/*': + schema: + type: string /admin/api/resources/{resource-id}/{representation-id}/data: get: tags: @@ -639,12 +1086,24 @@ paths: type: string format: uuid responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string /admin/api/resources/{resource-id}/data: get: tags: @@ -662,12 +1121,24 @@ paths: format: uuid example: a4212311-86e4-40b3-ace3-ef29cd687cf9 responses: + "404": + description: Not found + content: + '*/*': + schema: + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string "200": - description: OK + description: Ok content: '*/*': schema: - type: object + type: string put: tags: - 'Backend: Resource Data Handling' @@ -691,17 +1162,43 @@ paths: type: string example: Data String responses: - "200": - description: OK + "404": + description: Not found content: '*/*': schema: - type: object + type: string + "500": + description: Internal server error + content: + '*/*': + schema: + type: string + "400": + description: Invalid resource + content: + '*/*': + schema: + type: string + "201": + description: Resource created + content: + '*/*': + schema: + type: string components: schemas: BackendSource: type: object properties: + type: + type: string + description: Information of the backend system. + enum: + - local + - http-get + - https-get + - https-get-basicauth url: type: string format: uri @@ -709,8 +1206,6 @@ components: type: string password: type: string - system: - type: string description: Information of the backend system. oneOf: - $ref: '#/components/schemas/BackendSource' @@ -725,28 +1220,19 @@ components: byteSize: type: integer format: int32 - sourceType: + name: 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 + uuid: 55795317-0aaa-4fe1-b336-b2e26a00597f + type: JSON + byteSize: 101 + name: Example Representation source: + type: http-get 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: @@ -773,19 +1259,20 @@ components: version: type: string representations: - type: array - items: + type: object + additionalProperties: $ref: '#/components/schemas/ResourceRepresentation' description: Metadata of a resource example: - title: Sample Resource - description: This is an example resource containing weather data. - keywords: - - weather - - data - - sample - owner: https://openweathermap.org/ - license: http://opendatacommons.org/licenses/odbl/1.0/ - version: "1.0" + title: ExampleResource + description: ExampleResourceDescription + policy: Example policy + representations: + - uuid: 8e3a5056-1e46-42e1-a1c3-37aa08b2aedd + type: XML + byteSize: 101 + name: Example Representation + source: + type: local oneOf: - $ref: '#/components/schemas/ResourceMetadata' diff --git a/pom.xml b/pom.xml index c0abf3e72..601c810a6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,17 +10,17 @@ de.fraunhofer.isst dataspace-connector - ${project.version} + ${revision} dataspace-connector IDS Connector developed by the Fraunhofer ISST - https://gitlab.cc-asp.fraunhofer.de/fhg-isst-ids/dataspace-connector + https://www.dataspace-connector.io/ 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 + Github + https://github.com/FraunhoferISST/DataspaceConnector/issues @@ -29,8 +29,7 @@ Fraunhofer Institute for Software and Systems Engineering https://www.isst.fraunhofer.de/ - head - architect + lead developer @@ -43,23 +42,92 @@ 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 + ${licence_name} + ${licence_url} + ${licence_distribution} 2020 + + + + + no-tests + + true + + + + + + no-documentation + + true + + + + + + + release + + + false + false + + + + + maven-compiler-plugin + + true + true + true + true + + + -Xlint:all + -Xlint:-processing + + + + + + + + - 3.2.1 - 3.2.3 + 4.0.0 + 4.0.1 11 - ${java.version} - ${java.version} + ${java.version} 3.3.9 3.0.0-M2 @@ -67,28 +135,71 @@ 20190722 1.5.20 4.2.2 + + info@dataspace-connector.de + + 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.isst.ids.framework + messaging + ${ids-framework.version} + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + org.springframework.boot - spring-boot-starter + spring-boot-starter-actuator - ch.qos.logback - logback-classic + org.springframework.boot + spring-boot-starter-logging + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + org.springframework.boot spring-boot-starter-test @@ -98,22 +209,44 @@ com.vaadin.external.google android-json + + org.springframework.boot + spring-boot-starter-logging + org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + org.springframework.boot spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-logging + + org.springframework.boot spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-logging + + @@ -126,6 +259,11 @@ spring-boot-devtools + + org.springframework.boot + spring-boot-starter-log4j2 + + org.junit.jupiter @@ -152,12 +290,6 @@ ${org.json.version} - - - org.slf4j - slf4j-api - - io.swagger swagger-annotations @@ -271,17 +403,41 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + licence_type + licence_url + licence_distribution + ${project.build.directory} + + + src/main/resources - false + true conf/**/*.* - log4j.xml + log4j2.xml application.properties + + + **/*.p12 + + + + src/main/resources + false + + **/*.p12 + diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java index e7c58eef9..efff11c8d 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/ConnectorApplication.java @@ -10,48 +10,58 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + /** - * 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 + * 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.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" + "de.fraunhofer.isst.ids.framework.messaging", + "de.fraunhofer.isst.dataspaceconnector", + "de.fraunhofer.isst.ids.framework.communication", + "de.fraunhofer.isst.ids.framework.configuration", + "de.fraunhofer.isst.ids.framework.daps" }) 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. + * Creates the OpenAPI main description. The description contains general project information + * such as e.g. title, version and contact information. * - * @return The OpenAPI. + * @return The OpenAPI description. + * @throws IOException Throws an exception if the properties cannot be loaded from file. */ @Bean - public OpenAPI customOpenAPI() { + 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("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")) - ); + .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/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java index a0bd6b2a1..1b5359c82 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/ConfigurationAdapter.java @@ -8,30 +8,27 @@ 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 + * This class configures admin rights for all backend endpoints behind "/admin" using the role + * defined in {@link de.fraunhofer.isst.dataspaceconnector.config.MultipleEntryPointsSecurityConfig}. */ @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()); + .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 with an entry point for the admin realm + * @return The authentication entry point for the admin realm */ @Bean public AuthenticationEntryPoint authenticationEntryPoint() { diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java index 3fe8513c4..77f1aa4da 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/config/MultipleEntryPointsSecurityConfig.java @@ -11,10 +11,8 @@ 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 + * de.fraunhofer.isst.dataspaceconnector.config.ConfigurationAdapter}. */ @Configuration @EnableWebSecurity @@ -27,24 +25,22 @@ public class MultipleEntryPointsSecurityConfig { private String password; /** - *

userDetailsService.

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

encoder.

- * - * @return a {@link org.springframework.security.crypto.password.PasswordEncoder} object. + * Bean providing an password encoder + * @return The password encoder */ @Bean public PasswordEncoder encoder() { diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java index cf1d1914b..c0ffde797 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/BrokerController.java @@ -1,205 +1,298 @@ package de.fraunhofer.isst.dataspaceconnector.controller; +import de.fraunhofer.iais.eis.BaseConnectorImpl; +import de.fraunhofer.iais.eis.ConfigurationModelImpl; import de.fraunhofer.iais.eis.Resource; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; +import de.fraunhofer.iais.eis.ResourceCatalogBuilder; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.ids.framework.communication.broker.IDSBrokerService; 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 de.fraunhofer.isst.ids.framework.configuration.ConfigurationUpdateException; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; 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 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.ArrayList; import java.util.UUID; +import static de.fraunhofer.isst.dataspaceconnector.services.utils.ControllerUtils.*; + /** * 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") +@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; + private final DapsTokenProvider tokenProvider; + private final IDSBrokerService brokerService; + private final ResourceService resourceService; + private final ConfigurationContainer configurationContainer; - @Autowired /** - *

Constructor for BrokerController.

+ * 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. + * @param tokenProvider The token provider + * @param configurationContainer The container with the configuration + * @param offeredResourceService The service for the offered resources + * @param brokerService The service for the broker + * @throws IllegalArgumentException if any of the parameters is null. */ - public BrokerController(TokenProvider tokenProvider, ConfigurationContainer configurationContainer, OfferedResourceService offeredResourceService) { + @Autowired + public BrokerController(DapsTokenProvider tokenProvider, + ConfigurationContainer configurationContainer, + OfferedResourceServiceImpl offeredResourceService, + IDSBrokerService brokerService) + throws IllegalArgumentException { + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceService cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (brokerService == null) + throw new IllegalArgumentException("The IDSBrokerService cannot be null."); + 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()); - } + this.resourceService = offeredResourceService; + this.configurationContainer = configurationContainer; + this.brokerService = brokerService; } /** - * Sends a ConnectorAvailableMessage to an IDS broker. + * Notify an IDS broker of the availability of this connector. * * @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.") + @Operation(summary = "Register Connector", + description = "Register or update connector at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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) { + public ResponseEntity updateAtBroker(@Parameter(description = "The url of the broker." + , required = true, example = "https://broker.ids.isst.fraunhofer.de/infrastructure") + @RequestParam("broker") String url) { + // Make sure the request is authorized. + if (tokenProvider.getDAT() != 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); + updateConfigModel(); + // Send the update request to the broker. + final var brokerResponse = brokerService.updateSelfDescriptionAtBroker(url); + return new ResponseEntity<>(brokerResponse.body().string(), HttpStatus.OK); + } catch (ConfigurationUpdateException e) { + return respondUpdateError(url); + } catch (NullPointerException | IOException exception) { + return respondBrokerCommunicationFailed(exception); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // The request was unauthorized. + return respondRejectUnauthorized(url); } } /** - * Sends a ConnectorUnavailableMessage to an IDS broker. + * Notify an IDS broker that this connector is no longer available. * * @param url The broker address. * @return The broker response message or an error. */ @Operation(summary = "Unregister Connector", description = "Unregister connector at an IDS broker.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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) { + public ResponseEntity unregisterAtBroker( + @Parameter(description = "The url of the broker.", + required = true, example = "https://broker.ids.isst.fraunhofer.de/infrastructure") + @RequestParam("broker") String url) { + // Make sure the request is authorized. + if (tokenProvider.getDAT() != 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); + updateConfigModel(); + // Send the unregister request to the broker + final var brokerResponse = brokerService.unregisterAtBroker(url); + return new ResponseEntity<>(brokerResponse.body().string(), HttpStatus.OK); + } catch (ConfigurationUpdateException e) { + return respondUpdateError(url); + } catch (NullPointerException | IOException exception) { + return respondBrokerCommunicationFailed(exception); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // The request was unauthorized. + return respondRejectUnauthorized(url); } } /** - * Sends a QueryMessage to an IDS broker. + * Pass a query message 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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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" + - "}"; - + public ResponseEntity queryBroker( + @Parameter(description = "The url of the broker.", + required = true, example = "https://broker.ids.isst.fraunhofer.de/infrastructure") + @RequestParam("broker") String url, + @Schema(description = "Database query (SparQL)", required = true, + example = "SELECT ?subject ?predicate ?object\n" + + "FROM \n" + + "WHERE {\n" + + " ?subject ?predicate ?object\n" + + "};") @RequestBody String query) { + // Make sure the request is authorized. + if (tokenProvider.getDAT() != null) { + // Send the query request to the broker. 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); + final var brokerResponse = brokerService.queryBroker(url, query, + null, null, null); + return new ResponseEntity<>(brokerResponse.body().string(), HttpStatus.OK); + } catch (IOException exception) { + return respondBrokerCommunicationFailed(exception); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // The request was unauthorized. + return respondRejectUnauthorized(url); } } /** - * Sends a ResourceUpdateMessage to an IDS broker. + * Update a resource at an ids broker. * - * @param url The broker address. + * @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) + @Operation(summary = "Update Resource at Broker", + description = "Update an IDS 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")}) + @RequestMapping(value = "/update/{resource-id}", 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); - } - + 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) { + // Make sure the request is authorized. + if (tokenProvider.getDAT() != null) { 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); + // Get the resource + final var resource = + ((OfferedResourceServiceImpl) resourceService).getOfferedResources().get(resourceId); + if (resource == null) { + // The resource could not be found, reject and inform the requester. + return respondResourceNotFound(resourceId); + } else { + // The resource has been received, update at broker. + final var brokerResponse = brokerService.updateResourceAtBroker(url, resource); + return new ResponseEntity<>(brokerResponse.body().string(), HttpStatus.OK); + } + } catch (ClassCastException | NullPointerException exception) { + return respondResourceCouldNotBeLoaded(resourceId); + } catch (IOException exception) { + return respondBrokerCommunicationFailed(exception); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // The request was unauthorized. + return respondRejectUnauthorized(url); } } /** - * Sends a ResourceUpdateMessage to an IDS broker. + * Remove a resource from an ids broker * - * @param url The broker address. + * @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) + @Operation(summary = "Remove Resource from Broker", + description = "Remove an IDS 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")}) + @RequestMapping(value = "/remove/{resource-id}", 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; + 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) { + // Make sure the request is authorized. + if (tokenProvider.getDAT() != null) { 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); + // Get the resource + final var resource = + ((OfferedResourceServiceImpl) resourceService).getOfferedResources().get(resourceId); + if (resource == null) { + // The resource could not be found, reject and inform the requester. + return respondResourceNotFound(resourceId); + } else { + // The resource has been received, remove from broker. + final var brokerResponse = brokerService.removeResourceFromBroker(url, resource); + return new ResponseEntity<>(brokerResponse.body().string(), HttpStatus.OK); + } + } catch (ClassCastException | NullPointerException exception) { + return respondResourceCouldNotBeLoaded(resourceId); + } catch (IOException exception) { + return respondBrokerCommunicationFailed(exception); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // The request was unauthorized. + return respondRejectUnauthorized(url); } } + + /** + * Updates the connector object in the ids framework's config container. + * + * @throws ConfigurationUpdateException If the configuration could not be update. + */ + private void updateConfigModel() throws ConfigurationUpdateException { + BaseConnectorImpl connector = (BaseConnectorImpl) configurationContainer.getConnector(); + connector.setResourceCatalog(Util.asList(new ResourceCatalogBuilder() + ._offeredResource_((ArrayList) resourceService.getResources()) + .build())); + + ConfigurationModelImpl configurationModel = + (ConfigurationModelImpl) configurationContainer.getConfigModel(); + configurationModel.setConnectorDescription(connector); + + configurationContainer.updateConfiguration(configurationModel); + } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java index e7aa2b968..a028717be 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ConfigurationController.java @@ -3,8 +3,10 @@ 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 de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; 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.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,51 +17,87 @@ import java.io.IOException; +import static de.fraunhofer.isst.dataspaceconnector.services.utils.ControllerUtils.respondConfigurationNotFound; + /** * 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; + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationController.class); + + private final ConfigurationContainer configurationContainer; + private final SerializerProvider serializerProvider; + /** + * Constructor for ConfigurationController. + * + * @param configurationContainer The container with the configuration + * @param serializerProvider The provider for serialization + * @throws IllegalArgumentException if one of the parameters is null. + */ @Autowired - public ConfigurationController(ConfigurationContainer configurationContainer, SerializerProvider serializerProvider) { + public ConfigurationController(ConfigurationContainer configurationContainer, + SerializerProvider serializerProvider) throws IllegalArgumentException { + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + this.configurationContainer = configurationContainer; this.serializerProvider = serializerProvider; } @Hidden @RequestMapping(value = "/configuration", method = RequestMethod.POST) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @ResponseBody - public ResponseEntity updateConfiguration(@RequestBody String updatedConfiguration) { + public ResponseEntity updateConfiguration(@RequestBody String updatedConfiguration) { try { - ConfigurationModel configurationModel = serializerProvider.getSerializer().deserialize(updatedConfiguration, ConfigurationModel.class); - configurationContainer.updateConfiguration(configurationModel); + final var serializer = serializerProvider.getSerializer(); + if (serializer == null) { + throw new NullPointerException("No configuration serializer has been set."); + } + + final var new_configurationModel = + serializer.deserialize(updatedConfiguration, ConfigurationModel.class); + + configurationContainer.updateConfiguration(new_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); + } catch (NullPointerException exception) { + LOGGER.warn("Failed to receive the serializer. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to update configuration.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (IOException exception) { + LOGGER.warn("Failed to deserialize the configuration. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to update configuration.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ConfigurationUpdateException exception) { + LOGGER.warn("Failed to update the configuration. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to update configuration.", + HttpStatus.INTERNAL_SERVER_ERROR); } } @Hidden @RequestMapping(value = "/configuration", method = RequestMethod.GET) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found")}) @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); + public ResponseEntity getConfiguration() { + final var config = configurationContainer.getConfigModel(); + if (config != null) { + // Return the config + return new ResponseEntity<>(config.toRdf(), HttpStatus.OK); + } else { + return respondConfigurationNotFound(); } } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ExampleController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ExampleController.java new file mode 100644 index 000000000..dbed4c731 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ExampleController.java @@ -0,0 +1,283 @@ +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.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +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 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.net.URI; +import java.util.ArrayList; + +/** + * This class provides endpoints exposing example resources and configurations. + */ +@RestController +@RequestMapping("/admin/api/example") +@Tag(name = "Examples", description = "Endpoints for testing purpose") +public class ExampleController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExampleController.class); + + private final DapsTokenProvider tokenProvider; + private final PolicyHandler policyHandler; + + /** + * Constructor for ExampleController. + * + * @param tokenProvider The token provider + * @param policyHandler The service for handling policies + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public ExampleController(DapsTokenProvider tokenProvider, + PolicyHandler policyHandler) throws IllegalArgumentException { + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + this.tokenProvider = tokenProvider; + this.policyHandler = policyHandler; + } + + /** + * Get an example configuration. + * + * @return a {@link org.springframework.http.ResponseEntity} object. + */ + @Operation(summary = "Get Sample Connector configuration", + description = "Get a sample connector configuration for the config.json.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = "/configuration", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity getConnectorConfiguration() { + //NOTE: This needs some cleanup. Skip exception handling. + var 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.provideDapsToken().getBytes()) + .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); + } + + /** + * Get the policy pattern. + * + * @param policy a {@link java.lang.String} object. + * @return a {@link org.springframework.http.ResponseEntity} object. + */ + @Operation(summary = "Get pattern of policy", + description = "Get the policy pattern represented by a given JSON string.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = "/policy-validation", method = RequestMethod.POST) + @ResponseBody + public ResponseEntity getPolicyPattern( + @Parameter(description = "The JSON string representing a policy", required = true) + @RequestBody String policy) { + try { + // Return the policy pattern + return new ResponseEntity<>(policyHandler.getPattern(policy), HttpStatus.OK); + } catch (ContractException exception) { + // Failed to receive the pattern. Inform the requester. + LOGGER.error("Failed to read policy.", exception); + return new ResponseEntity<>("The policy is invalid.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Get an example policy pattern. + * + * @param pattern a {@link 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.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = "/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/MainController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java index 1bf611592..cae9a7ada 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/MainController.java @@ -1,17 +1,21 @@ 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.BaseConnectorImpl; +import de.fraunhofer.iais.eis.ResourceCatalog; +import de.fraunhofer.iais.eis.ResourceCatalogBuilder; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; 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.exceptions.ConnectorConfigurationException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.NegotiationService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; 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 de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,270 +25,221 @@ 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 static final Logger LOGGER = LoggerFactory.getLogger(MainController.class); - private OfferedResourceService offeredResourceService; - private RequestedResourceService requestedResourceService; + private final SerializerProvider serializerProvider; + private final ResourceService offeredResourceService, requestedResourceService; + private final IdsUtils idsUtils; + private final NegotiationService negotiationService; + private final PolicyHandler policyHandler; - private PolicyHandler policyHandler; - - @Autowired /** - *

Constructor for MainController.

+ * 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. + * @param serializerProvider The provider for serialization + * @param offeredResourceService The service for the offered resources + * @param requestedResourceService The service for the requested resources + * @param idsUtils The utilities for ids messages + * @param negotiationService The service for negotiations + * @param policyHandler The service for handling policies + * @throws IllegalArgumentException if one of the parameters is null. */ - public MainController(TokenProvider tokenProvider, ConfigurationContainer configurationContainer, - SerializerProvider serializerProvider, OfferedResourceService offeredResourceService, - RequestedResourceService requestedResourceService, PolicyHandler policyHandler) { - this.tokenProvider = tokenProvider; - this.configurationContainer = configurationContainer; + @Autowired + public MainController(SerializerProvider serializerProvider, + OfferedResourceServiceImpl offeredResourceService, + RequestedResourceServiceImpl requestedResourceService, + IdsUtils idsUtils, NegotiationService negotiationService, + PolicyHandler policyHandler) throws IllegalArgumentException { + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceService cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The RequestedResourceService cannot be null."); + + if (idsUtils == null) + throw new IllegalArgumentException("The IdsUtils cannot be null."); + + if (negotiationService == null) + throw new IllegalArgumentException("The NegotiationService cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + this.serializerProvider = serializerProvider; this.offeredResourceService = offeredResourceService; this.requestedResourceService = requestedResourceService; + this.idsUtils = idsUtils; + this.negotiationService = negotiationService; this.policyHandler = policyHandler; } + /** + * Gets connector self-description without catalog. + * + * @return Self-description or error response. + */ + @Operation(summary = "Public Endpoint for Connector Self-description", + description = "Get the connector's reduced self-description.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = {"/", ""}, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity getPublicSelfDescription() { + try { + // Modify a connector for exposing the reduced self-description + var connector = (BaseConnectorImpl) idsUtils.getConnector(); + connector.setResourceCatalog(null); + connector.setPublicKey(null); + + return new ResponseEntity<>(serializerProvider.getSerializer().serialize(connector), + HttpStatus.OK); + } catch (ConnectorConfigurationException exception) { + // No connector found + LOGGER.warn("No connector has been configurated."); + return new ResponseEntity<>("No connector is currently available.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (IOException exception) { + // Could not serialize the connector. + LOGGER.warn("Could not serialize the connector. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("No connector is currently available.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + } + /** * 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) + @Operation(summary = "Connector Self-description", + description = "Get the connector's self-description.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = {"/admin/api/connector"}, method = RequestMethod.GET) @ResponseBody - public ResponseEntity getSelfService() { + 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); + // Modify a connector for exposing a resource catalog + var connector = (BaseConnectorImpl) idsUtils.getConnector(); + connector.setResourceCatalog(Util.asList(buildResourceCatalog())); + + return new ResponseEntity<>(serializerProvider.getSerializer().serialize(connector), + HttpStatus.OK); + } catch (ConnectorConfigurationException exception) { + // No connector found + LOGGER.warn("No connector has been configurated."); + return new ResponseEntity<>("No connector is currently available.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (IOException exception) { + // Could not serialize the connector. + LOGGER.warn("Could not serialize the connector. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("No connector is currently available.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** - *

getConnector.

+ * Turns policy negotiation on or off. * - * @return a {@link org.springframework.http.ResponseEntity} object. + * @param status The desired state. + * @return Http ok or error response. */ - @Operation(summary = "Get Connector configuration", description = "Get the connector's configuration.") - @RequestMapping(value = "/example/configuration", method = RequestMethod.GET) + @Operation(summary = "Endpoint for Policy Negotiation Status", + description = "Turn the policy negotiation on or off.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = {"/admin/api/negotiation"}, method = RequestMethod.PUT) @ResponseBody - public ResponseEntity getConnector() { - ArrayList exceptions = new ArrayList<>(); - exceptions.add(URI.create("https://localhost:8080/")); - exceptions.add(URI.create("http://localhost:8080/")); + public ResponseEntity setNegotiationStatus(@RequestParam("status") boolean status) { + negotiationService.setStatus(status); - 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); + if (negotiationService.isStatus()) { + return new ResponseEntity<>("Policy Negotiation was turned on.", HttpStatus.OK); + } else { + return new ResponseEntity<>("Policy Negotiation was turned off.", HttpStatus.OK); + } } /** - *

getPolicyPattern.

+ * Returns the policy negotiation status. * - * @param policy a {@link java.lang.String} object. - * @return a {@link org.springframework.http.ResponseEntity} object. - * @throws java.io.IOException if any. + * @return Http ok or error response. */ - @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) + @Operation(summary = "Endpoint for Policy Negotiation Status Check", + description = "Return the policy negotiation status.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = {"/admin/api/negotiation"}, method = RequestMethod.GET) @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); + public ResponseEntity getNegotiationStatus() { + if (negotiationService.isStatus()) { + return new ResponseEntity<>("Policy Negotiation is turned on.", HttpStatus.OK); + } else { + return new ResponseEntity<>("Policy Negotiation is turned off.", HttpStatus.OK); + } } /** - *

getExampleUsagePolicy.

+ * Allows requesting data without policy enforcement. * - * @param pattern a {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler.Pattern} object. - * @return a {@link org.springframework.http.ResponseEntity} object. + * @param status The desired state. + * @return Http ok or error response. */ - @Operation(summary = "Get example policy", description = "Get an example policy for a given policy pattern.") - @RequestMapping(value = "/example/usage-policy", method = RequestMethod.POST) + @Operation(summary = "Endpoint for Allowing Unsupported Patterns", description = "Allow " + + "requesting data without policy enforcement if an unsupported pattern is recognized.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = {"/admin/api/ignore-unsupported-patterns"}, method = RequestMethod.PUT) @ResponseBody - public ResponseEntity getExampleUsagePolicy(@Parameter(description = "The policy pattern.", required = true) - @RequestParam("pattern") PolicyHandler.Pattern pattern) { - ContractOffer contractOffer = null; + public ResponseEntity getPatternStatus(@RequestParam("status") boolean status) { + policyHandler.setIgnoreUnsupportedPatterns(status); - 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; + if (policyHandler.isIgnoreUnsupportedPatterns()) { + return new ResponseEntity<>("Data can be accessed despite unsupported pattern.", + HttpStatus.OK); + } else { + return new ResponseEntity<>("Data cannot be accessed with an unsupported pattern.", + HttpStatus.OK); } - return new ResponseEntity<>(contractOffer.toRdf(), HttpStatus.OK); + } + + /** + * Returns the unsupported pattern status. + * + * @return Http ok or error response. + */ + @Operation(summary = "Endpoint for Pattern Checking", + description = "Return if unsupported patterns are ignored when requesting data.") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Ok") }) + @RequestMapping(value = {"/admin/api/ignore-unsupported-patterns"}, method = RequestMethod.GET) + @ResponseBody + public ResponseEntity getPatternStatus() { + if (policyHandler.isIgnoreUnsupportedPatterns()) { + return new ResponseEntity<>("Data can be accessed despite unsupported pattern.", + HttpStatus.OK); + } else { + return new ResponseEntity<>("Data cannot be accessed with an unsupported pattern.", + HttpStatus.OK); + } + } + + private ResourceCatalog buildResourceCatalog() throws ConstraintViolationException { + return new ResourceCatalogBuilder() + ._offeredResource_(new ArrayList<>(offeredResourceService.getResources())) + ._requestedResource_(new ArrayList<>(requestedResourceService.getResources())) + .build(); } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java index f9db450a4..4bdebee60 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/RequestController.java @@ -1,12 +1,24 @@ 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 de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageResponseException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.MessageService.ResponseType; +import de.fraunhofer.isst.dataspaceconnector.services.messages.NegotiationService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.ArtifactMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.ContractMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.DescriptionMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; 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 okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -14,122 +26,383 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.io.IOException; import java.net.URI; +import java.util.Map; 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") +@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; + private static final Logger LOGGER = LoggerFactory.getLogger(RequestController.class); + + private final DapsTokenProvider tokenProvider; + private final ArtifactMessageService artifactMessageService; + private final DescriptionMessageService descriptionMessageService; + private final ContractMessageService contractMessageService; + private final NegotiationService negotiationService; + private final ResourceService resourceService; - @Autowired /** - *

Constructor for RequestController.

+ * 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. + * @param tokenProvider The token provider + * @param artifactMessageService The service for artifact messages + * @param descriptionMessageService The service for description messages + * @param contractMessageService The service for contract messages + * @param negotiationService The service for negotiations + * @param requestedResourceService The service for the requested resources + * @throws IllegalArgumentException if any of the parameters is null. */ - public RequestController(TokenProvider tokenProvider, ConnectorRequestServiceImpl requestMessageService, ConnectorRequestServiceUtils connectorRequestServiceUtils) { + @Autowired + public RequestController(DapsTokenProvider tokenProvider, + ArtifactMessageService artifactMessageService, + DescriptionMessageService descriptionMessageService, + ContractMessageService contractMessageService, + NegotiationService negotiationService, + RequestedResourceServiceImpl requestedResourceService) + throws IllegalArgumentException { + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + if (artifactMessageService == null) + throw new IllegalArgumentException("The ArtifactMessageService cannot be null."); + + if (descriptionMessageService == null) + throw new IllegalArgumentException("The DescriptionMessageService cannot be null."); + + if (contractMessageService == null) + throw new IllegalArgumentException("The ContractMessageService cannot be null."); + + if (negotiationService == null) + throw new IllegalArgumentException("The NegotiationService cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The RequestedResourceServiceImpl cannot be null."); + this.tokenProvider = tokenProvider; - this.requestMessageService = requestMessageService; - this.connectorRequestServiceUtils = connectorRequestServiceUtils; + this.artifactMessageService = artifactMessageService; + this.descriptionMessageService = descriptionMessageService; + this.contractMessageService = contractMessageService; + this.negotiationService = negotiationService; + this.resourceService = requestedResourceService; } /** - * Actively requests data from an external connector by building an ArtifactRequestMessage. + * Requests metadata from an external connector by building an ArtifactRequestMessage. * * @param recipient The target connector uri. - * @param requestedArtifact The requested resource uri. + * @param resourceId 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) + @Operation(summary = "Description Request", + description = "Request metadata from another IDS connector.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = "/description", 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); + 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.", + example = "https://w3id.org/idsa/autogen/resource/a4212311-86e4-40b3-ace3-ef29cd687cf9") + @RequestParam(value = "requestedResource", required = false) URI resourceId) { + if (tokenProvider.getDAT() == null) { + return respondRejectUnauthorized(recipient, resourceId); + } + + Map response; + try { + // Send DescriptionRequestMessage. + descriptionMessageService.setRequestParameters(recipient, resourceId); + response = descriptionMessageService.sendMessage(""); + } catch (MessageBuilderException exception) { + // Failed to send the description request message. + LOGGER.info("Failed to send or build a request. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to send description request message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (MessageResponseException exception) { + // Failed to read the description response message. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + String header, payload; + try { + header = response.get("header"); + payload = response.get("payload"); + } catch (Exception exception) { + // Failed to read the message parts. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + // Get response message type. + final var messageType = descriptionMessageService.getResponseType(header); + if (messageType != ResponseType.DESCRIPTION_RESPONSE) + return returnRejectionMessage(messageType, response); + + if (resourceId != null) { + // Save metadata to database. + try { + final var validationKey = descriptionMessageService + .saveMetadata(payload, resourceId); + return new ResponseEntity<>("Validation: " + validationKey + + "\nResponse: " + payload, HttpStatus.OK); + } catch (InvalidResourceException exception) { + LOGGER.info("Could not save metadata to database. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>(exception.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); } } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + // Return self-description. + return new ResponseEntity<>(payload, HttpStatus.OK); + } + } + + /** + * Sends a contract request to a connector by building an ContractRequestMessage. + * + * @param recipient The URI of the requested IDS connector. + * @param artifactId The URI of the requested artifact. + * @param contractOffer The contract offer for the requested resource. + * @return OK or error response. + */ + @Operation(summary = "Contract Request", + description = "Send a contract request to another IDS connector.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = "/contract", method = RequestMethod.POST) + @ResponseBody + public ResponseEntity requestContract( + @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 artifactId, + @Parameter(description = "The contract offer for the requested resource.") + @RequestBody(required = false) String contractOffer) { + if (tokenProvider.getDAT() == null) { + return respondRejectUnauthorized(recipient, null); + } + + Map response; + try { + // Start policy negotiation. + final var request = negotiationService.buildContractRequest(contractOffer, artifactId); + // Send ContractRequestMessage. + contractMessageService.setRequestParameters(recipient, request.getId()); + response = contractMessageService.sendMessage(request.toRdf()); + } catch (IllegalArgumentException exception) { + LOGGER.warn("Failed to build contract request. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to build contract request.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (MessageBuilderException exception) { + // Failed to send the contract request message. + LOGGER.info("Failed to send or build a request. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to send contract request message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (MessageResponseException exception) { + // Failed to read the contract response message. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + String header, payload; + try { + header = response.get("header"); + payload = response.get("payload"); + } catch (Exception exception) { + // Failed to read the message parts. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + // Get response message type. + final var messageType = contractMessageService.getResponseType(header); + // TODO Add further responses (on contract offer or contract response). + if (messageType != ResponseType.CONTRACT_AGREEMENT) + return returnRejectionMessage(messageType, response); + + // Get contract id. + URI agreementId; + try { + agreementId = negotiationService.contractAccepted(recipient, header, payload); + } catch (ContractException exception) { + // Failed to read the contract. + LOGGER.info("Could not read contract. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the received contract.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (MessageException exception) { + // Failed to send contract agreement confirmation. + LOGGER.info("Failed to send contract agreement. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Negotiation sequence was not fully completed.", + HttpStatus.INTERNAL_SERVER_ERROR); } + + if (agreementId == null) { + // Failed to read the contract agreement. + LOGGER.info("Received invalid contract agreement."); + return new ResponseEntity<>("Received invalid contract agreement.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + return new ResponseEntity<>(String.valueOf(agreementId), HttpStatus.OK); } /** - * Actively requests metadata from an external connector by building an ArtifactRequestMessage. + * Requests data from an external connector by building an ArtifactRequestMessage. * * @param recipient The target connector uri. - * @param requestedArtifact The requested resource uri. + * @param artifactId The requested artifact uri. + * @param contractId The URI of the contract agreement. + * @param key a {@link java.util.UUID} object. * @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) + @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "403", description = "Forbidden"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = "/artifact", 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); + 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 artifactId, + @Parameter(description = "The URI of the contract agreement.", + example = "https://w3id.org/idsa/autogen/contractAgreement/a4212311-86e4-40b3-ace3-ef29cd687cf9") + @RequestParam(value = "transferContract", required = false) URI contractId, + @Parameter(description = "A unique validation key.", required = true) + @RequestParam("key") UUID key) { + if (tokenProvider.getDAT() == null) { + return respondRejectUnauthorized(recipient, artifactId); + } + + if (!resourceExists(key)) { + // The resource does not exist. + LOGGER.warn(String.format("Failed data request due to invalid key.\nRecipient: " + + "%s\nrequestedArtifact:%s\nkey:%s", recipient.toString(), + artifactId.toString(), key.toString())); + return new ResponseEntity<>("Your key is not valid. Please request metadata first.", + HttpStatus.FORBIDDEN); + } + + Map response; + try { + // Send ArtifactRequestMessage. + artifactMessageService.setRequestParameters(recipient, artifactId, contractId); + response = artifactMessageService.sendMessage(""); + } catch (MessageBuilderException exception) { + // Failed to send the artifact request message. + LOGGER.info("Failed to send or build a request. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to send artifact request message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (MessageResponseException exception) { + // Failed to read the artifact response message. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + String header, payload; + try { + header = response.get("header"); + payload = response.get("payload"); + } catch (Exception exception) { + // Failed to read the message parts. + LOGGER.info("Received invalid ids response. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Failed to read the ids response message.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + // Get response message type. + final var messageType = artifactMessageService.getResponseType(header); + if (messageType != ResponseType.ARTIFACT_RESPONSE) + return returnRejectionMessage(messageType, response); + + try { + // Save data to database. + artifactMessageService.saveData(payload, key); + return new ResponseEntity<>(String.format("Saved at: %s\nResponse: " + + "%s", key, payload), HttpStatus.OK); + } catch (ResourceException exception) { + LOGGER.warn("Could not save data to database. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("Failed to save to database.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * The request was unauthorized. + * + * @param recipient The recipient url. + * @param requestedArtifact The id of the requested artifact. + * @return An http response. + */ + private ResponseEntity respondRejectUnauthorized(URI recipient, URI requestedArtifact) { + LOGGER.debug( + "Unauthorized call. No DAT token found. [recipient=({}), requestedArtifact=({})]", + recipient.toString(), requestedArtifact.toString()); + + return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + } + + /** + * Checks for rejection or contract rejection message. + * + * @param responseType The type of the response + * @param response The response content + * @return The response message + */ + private ResponseEntity returnRejectionMessage(ResponseType responseType, + Map response) { + if (responseType == ResponseType.REJECTION) { + return new ResponseEntity<>(ResponseType.REJECTION + ": " + + response.get("payload"), HttpStatus.OK); + } else if (responseType == ResponseType.CONTRACT_REJECTION) { + return new ResponseEntity<>(ResponseType.CONTRACT_REJECTION + ": " + + response.get("payload"), HttpStatus.OK); } else { - LOGGER.error("No DAT token found"); - return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>("Unexpected response: \n" + response, HttpStatus.OK); + } + } + + /** + * Checks if a resource exists. + * + * @param resourceId The resource uuid. + * @return true if the resource exists. + */ + private boolean resourceExists(UUID resourceId) { + try { + return resourceService.getResource(resourceId) != null; + } catch (ResourceException exception) { + return false; } } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java index 9b3102328..6a6331d9f 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceController.java @@ -1,12 +1,21 @@ package de.fraunhofer.isst.dataspaceconnector.controller; +import de.fraunhofer.isst.dataspaceconnector.exceptions.RequestFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.UnsupportedPatternException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceAlreadyExistsException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; 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.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; 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.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,39 +24,44 @@ 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 + * This class provides endpoints for the internal resource handling. Resources can be created and + * modified with it's {@link ResourceMetadata} including {@link de.fraunhofer.iais.eis.Contract} and + * {@link ResourceRepresentation}. */ @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 static final Logger LOGGER = LoggerFactory.getLogger(ResourceController.class); - private PolicyHandler policyHandler; + private final ResourceService offeredResourceService, requestedResourceService; + private final PolicyHandler policyHandler; - @Autowired /** - *

Constructor for ResourceController.

+ * 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. + * @param offeredResourceService The service for the offered resources + * @param policyHandler The service for handling policies + * @param requestedResourceService The service for the requested resources + * @throws IllegalArgumentException if any of the parameters is null. */ - public ResourceController(OfferedResourceService offeredResourceService, - PolicyHandler policyHandler, RequestedResourceService requestedResourceService) { + @Autowired + public ResourceController(OfferedResourceServiceImpl offeredResourceService, + PolicyHandler policyHandler, RequestedResourceServiceImpl requestedResourceService) + throws IllegalArgumentException { + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceService cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The RequestedResourceService cannot be null."); + this.offeredResourceService = offeredResourceService; this.requestedResourceService = requestedResourceService; this.policyHandler = policyHandler; @@ -57,96 +71,163 @@ public ResourceController(OfferedResourceService offeredResourceService, * Registers a resource with its metadata and, if wanted, with an already existing id. * * @param resourceMetadata The resource metadata. - * @param uuid The resource uuid. + * @param uuid The resource uuid. * @return The added uuid. */ @Operation(summary = "Register Resource", description = "Register a resource by its metadata.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Resource created"), + @ApiResponse(responseCode = "400", description = "Invalid resource"), + @ApiResponse(responseCode = "409", description = "Resource already exists"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @RequestMapping(value = "/resource", method = RequestMethod.POST) @ResponseBody - public ResponseEntity createResource(@RequestBody ResourceMetadata resourceMetadata, @RequestParam(value = "id", required = false) UUID uuid) { + 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); + ((OfferedResourceServiceImpl) offeredResourceService).addResourceWithId(resourceMetadata, uuid); + return new ResponseEntity<>(uuid.toString(), HttpStatus.CREATED); } else { - return new ResponseEntity<>("Resource registered with uuid: " + offeredResourceService.addResource(resourceMetadata), HttpStatus.CREATED); + final var newUuid = offeredResourceService.addResource(resourceMetadata); + return new ResponseEntity<>(newUuid.toString(), HttpStatus.CREATED); } - } catch (Exception e) { - LOGGER.error("Resource could not be registered: {}", e.getMessage()); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } catch (InvalidResourceException exception) { + LOGGER.debug("Failed to add resource. The resource is not valid. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The resource could not be added.", + HttpStatus.BAD_REQUEST); + } catch (ResourceAlreadyExistsException exception) { + LOGGER.debug("Failed to add resource. The resource already exists. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The resource could not be added. It already exists.", + HttpStatus.CONFLICT); + } catch (ResourceException exception) { + LOGGER.warn("Failed to add resource. Something went wrong. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The resource could not be added.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Updates resource metadata by id. * - * @param id The resource id. + * @param resourceId 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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Invalid resource"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + public ResponseEntity updateResource( + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId, + @RequestBody ResourceMetadata resourceMetadata) { 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); + ((OfferedResourceServiceImpl) offeredResourceService).updateResource(resourceId, resourceMetadata); + return new ResponseEntity<>("Resource was updated successfully.", HttpStatus.OK); + } catch (InvalidResourceException exception) { + LOGGER.debug("Failed to update the resource. The resource is not valid. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be updated.", + HttpStatus.BAD_REQUEST); + } catch (ResourceNotFoundException exception) { + LOGGER.debug("Failed to update the resource. The resource could not be found." + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Resource could not be updated.", HttpStatus.NOT_FOUND); + } catch (ResourceException exception) { + LOGGER.warn("Failed to update the resource. Something went wrong." + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Resource could not be updated.", + 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. + * @param resourceId The resource id. + * @return Metadata or an error response. */ @Operation(summary = "Get Resource", description = "Get the resource's metadata by its uuid.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId) { 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); + // Try to find the data in the offeredResourceService + return new ResponseEntity<>( + offeredResourceService.getMetadata(resourceId), HttpStatus.OK); + } catch (ResourceNotFoundException offeredResourceServiceException) { + LOGGER.debug("Failed to receive the resource from the OfferedResourcesService." + + " [exception=({})]", offeredResourceServiceException.getMessage()); + try { + // Try to find the data in the requestedResourceService + return new ResponseEntity<>( + requestedResourceService.getMetadata(resourceId), HttpStatus.OK); + } catch (ResourceNotFoundException requestedResourceServiceException) { + LOGGER + .debug("Failed to receive the resource from the RequestedResourcesService." + + " [exception=({})]", requestedResourceServiceException.getMessage()); + // The data could not be found in the offeredResourceService and requestedResourceService + LOGGER.debug("Failed to receive the resource. The resource does not exist."); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } } + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to receive the resource. The resource is not valid." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>( + "The resource could not be received. Not a valid resource format.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ResourceException exception) { + LOGGER.warn("Failed to receive the resource. Something went wrong. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Resource could not be received.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Deletes resource by id. * - * @param id The resource id. + * @param resourceId 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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found")}) @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); + public ResponseEntity deleteResource( + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId) { + if (offeredResourceService.deleteResource(resourceId)) { + return new ResponseEntity<>("Resource was deleted successfully.", + HttpStatus.OK); + } else { + LOGGER.debug("Failed to delete the resource from the OfferedResourcesService."); + if (requestedResourceService.deleteResource(resourceId)) { + return new ResponseEntity<>("Resource was deleted successfully.", HttpStatus.OK); + } else { + LOGGER.debug("Failed to delete the resource from the RequestedResourcesService."); + LOGGER.debug("Failed to delete the resource. The resource does not exist."); + return new ResponseEntity<>("The resource could not be found.", + HttpStatus.NOT_FOUND); } } } @@ -155,28 +236,48 @@ public ResponseEntity deleteResource( * Updates usage policy. * * @param resourceId The resource id. + * @param policy The resource's usage policy as string. * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Invalid resource"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + 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) { 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); + ((OfferedResourceServiceImpl) offeredResourceService).updateContract(resourceId, policy); + return new ResponseEntity<>("Contract was updated successfully.", HttpStatus.OK); + } catch (UnsupportedPatternException | RequestFormatException exception) { + // The policy is not in the correct format. + LOGGER.debug("Failed to update the resource contract. The policy is malformed. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Policy syntax error.", HttpStatus.BAD_REQUEST); + } catch (ResourceNotFoundException exception) { + // The resource could not be found. + LOGGER.debug("Failed to update the resource contract. The resource does not exist. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be found.", + HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to update the resource contract. The resource is not valid. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.BAD_REQUEST); + } catch (ResourceException exception) { + LOGGER.warn("Failed to update the resource contract. Something went wrong. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Resource could not be updated.", + HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -185,138 +286,297 @@ public ResponseEntity updateContract( * * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + public ResponseEntity getContract( + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId) { 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); + // Try to find the data in the offeredResourceService + final var policy = offeredResourceService.getMetadata(resourceId).getPolicy(); + return new ResponseEntity<>(policy, HttpStatus.OK); + } catch (ResourceNotFoundException offeredResourceServiceException) { + LOGGER.debug("Failed to receive the resource from the OfferedResourcesService." + + " [exception=({})]", offeredResourceServiceException.getMessage()); + try { + // Try to find the data in the requestedResourceService + final var policy = requestedResourceService.getMetadata(resourceId).getPolicy(); + return new ResponseEntity<>(policy, HttpStatus.OK); + } catch (ResourceNotFoundException requestedResourceServiceException) { + // The data could not be found in the offeredResourceService and requestedResourceService + LOGGER + .debug("Failed to receive the resource from the RequestedResourcesService." + + "exception=({})]", requestedResourceServiceException.getMessage()); + LOGGER.debug( + "Failed to receive the resource contract. The resource does not exist."); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } } + } catch (ResourceNotFoundException exception) { + // The resource could not be found. + LOGGER.debug("Failed to receive the resource contract. The resource does not exist." + + "exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be found.", HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to receive the resource contract. The resource is not valid. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ResourceException exception) { + LOGGER.warn("Failed to receive the resource contract. Something went wrong. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Contract could not be received.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** - *

getAccess.

+ * Get how often resource data has been accessed. * * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + public ResponseEntity getAccess( + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId) { try { - return new ResponseEntity<>(requestedResourceService.getResource(resourceId).getAccessed(), HttpStatus.OK); - } catch (Exception f) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); + final var resource = ((RequestedResourceServiceImpl) requestedResourceService).getResource(resourceId); + if (resource == null) { + LOGGER + .debug("Failed to received the resource access. The resource does not exist."); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(resource.getAccessed(), HttpStatus.OK); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to receive the resource access. The resource is not valid. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ResourceException exception) { + LOGGER.warn("Failed to receive the resource access. Something went wrong." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("Access counter could not be received.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Adds resource representation. * - * @param resourceId The resource id. + * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Representation created"), + @ApiResponse(responseCode = "400", description = "Invalid representation"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "409", description = "Representation already exists"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + 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) { try { - if(uuid != null){ - offeredResourceService.addRepresentationWithId(resourceId, representation, uuid); - }else { - uuid = offeredResourceService.addRepresentation(resourceId, representation); + UUID newUuid; + if (uuid != null) { + newUuid = ((OfferedResourceServiceImpl) offeredResourceService) + .addRepresentationWithId(resourceId, representation, uuid); + } else { + newUuid = ((OfferedResourceServiceImpl) 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); + + return new ResponseEntity<>(newUuid.toString(), HttpStatus.CREATED); + } catch (ResourceAlreadyExistsException exception) { + LOGGER.debug("Failed to add resource representation. The representation already exists." + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be added. It already exits.", + HttpStatus.CONFLICT); + } catch (ResourceNotFoundException exception) { + // The resource could not be found. + LOGGER.debug( + "Failed to add resource representation. The resource does not exist. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The representation could not be found.", + HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug( + "Failed to add resource representation. The resource is not valid. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.BAD_REQUEST); + } catch (ResourceException exception) { + LOGGER.warn( + "Failed to add resource representation. Something went wrong. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The representation could not be added.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Updates resource representation. * - * @param resourceId The resource id. + * @param resourceId The resource id. * @param representationId The representation id. - * @param representation A new representation. + * @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.") + @Operation(summary = "Update representation", + description = "Update a resource's representation by its uuid.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "400", description = "Invalid representation"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + 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) { 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); + ((OfferedResourceServiceImpl) offeredResourceService) + .updateRepresentation(resourceId, representationId, representation); + return new ResponseEntity<>("Representation was updated successfully.", HttpStatus.OK); + } catch (ResourceNotFoundException exception) { + LOGGER + .debug("Failed to update the resource representation. The resource does not exist." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be found.", + HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to update the resource representation. The resource is not valid." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.BAD_REQUEST); + } catch (ResourceException exception) { + LOGGER.warn("Failed to update the resource representation. Something went wrong." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be updated.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Adds resource representation. * - * @param resourceId The resource id. + * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + @Parameter(description = "The resource uuid.", required = true) + @PathVariable("resource-id") UUID resourceId, + @Parameter(description = "The representation uuid.", required = true) + @PathVariable("representation-id") UUID representationId) { try { - return new ResponseEntity<>(offeredResourceService.getRepresentation(resourceId, representationId), HttpStatus.OK); - } catch (Exception e) { - return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); + final var representation = + offeredResourceService.getRepresentation(resourceId, representationId); + return new ResponseEntity<>(representation, HttpStatus.OK); + } catch (ResourceNotFoundException exception) { + // The resource could not be found. + LOGGER.debug( + "Failed to received the resource representation. The resource does not exist. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be found.", + HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER + .debug("Failed to received the resource representation. The resource is not valid. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ResourceException exception) { + LOGGER.warn("Failed to received the resource representation. Something went wrong. " + + "[exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be received.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Removes resource representation. * - * @param resourceId The resource id. + * @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.") + @Operation(summary = "Remove Resource Representation", + description = "Remove a resource's representation by its uuid.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + 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) { 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); + if (((OfferedResourceServiceImpl) offeredResourceService).deleteRepresentation(resourceId, representationId)) { + return new ResponseEntity<>("Representation was deleted successfully.", + HttpStatus.OK); + } else { + return new ResponseEntity<>("The representation could not be found.", + HttpStatus.NOT_FOUND); + } + } catch (ResourceNotFoundException exception) { + // The resource could not be found. + LOGGER.debug( + "Failed to delete the resource representation. The resource could not be found." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be found.", + HttpStatus.NOT_FOUND); + } catch (InvalidResourceException exception) { + // The resource has been found but is in an invalid format. + LOGGER.debug("Failed to delete the resource representation. The resource is not valid." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The resource could not be received. Not a " + + "valid resource format.", HttpStatus.INTERNAL_SERVER_ERROR); + } catch (ResourceException exception) { + LOGGER.warn("Failed to delete the resource representation. Something went wrong." + + " [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The representation could not be received.", + HttpStatus.INTERNAL_SERVER_ERROR); } } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java index 1ff1225ea..d5e77873a 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/controller/ResourceDataController.java @@ -1,9 +1,16 @@ package de.fraunhofer.isst.dataspaceconnector.controller; -import de.fraunhofer.isst.dataspaceconnector.services.resource.OfferedResourceService; -import de.fraunhofer.isst.dataspaceconnector.services.resource.RequestedResourceService; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,30 +21,36 @@ import java.util.UUID; +import static de.fraunhofer.isst.dataspaceconnector.services.utils.ControllerUtils.respondResourceNotFound; + /** * 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; + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceDataController.class); + + private final ResourceService offeredResourceService, requestedResourceService; - @Autowired /** - *

Constructor for ResourceDataController.

+ * 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. + * @param offeredResourceService The service for the offered resources + * @param requestedResourceService The service for the requested resources + * @throws IllegalArgumentException if any of the parameters is null. */ - public ResourceDataController(OfferedResourceService offeredResourceService, RequestedResourceService requestedResourceService) { + @Autowired + public ResourceDataController(OfferedResourceServiceImpl offeredResourceService, + RequestedResourceServiceImpl requestedResourceService) throws IllegalArgumentException { + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceService cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The RequestedResourceService cannot be null."); + this.offeredResourceService = offeredResourceService; this.requestedResourceService = requestedResourceService; } @@ -48,23 +61,37 @@ public ResourceDataController(OfferedResourceService offeredResourceService, Req * @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 + @Operation(summary = "Publish Resource Data String", + description = "Publish resource data as string.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Resource created"), + @ApiResponse(responseCode = "400", description = "Invalid resource"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) + @RequestMapping(value = "/{resource-id}/data", method = {RequestMethod.PUT}) @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 { + 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) { 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); + return new ResponseEntity<>("", HttpStatus.CREATED); + } catch (ResourceNotFoundException exception) { + return respondResourceNotFound(id); + } catch (InvalidResourceException exception) { + LOGGER.debug("Resource is not valid. [id=({}), exception=({})]", id, + exception.getMessage()); + return new ResponseEntity<>("Failed to store resource. Resource not valid.", + HttpStatus.BAD_REQUEST); + } catch (ResourceException exception) { + LOGGER.warn("Caught unhandled resource exception. [exception=({})]", + exception.getMessage()); + return new ResponseEntity<>("The resource could not be published.", + HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -73,51 +100,110 @@ public ResponseEntity publishResource( * * @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.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + public ResponseEntity getDataById(@Parameter(description = "The resource uuid.", + required = true, example = "a4212311-86e4-40b3-ace3-ef29cd687cf9") + @PathVariable("resource-id") UUID id) { 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); + return new ResponseEntity<>(offeredResourceService.getData(id), HttpStatus.OK); + } catch (ResourceNotFoundException offeredResourceServiceException) { + LOGGER.debug( + "Could not find resource in OfferedResourceService. [id=({}), exception=({})]", + id, offeredResourceServiceException.getMessage()); + + try { + return new ResponseEntity<>(requestedResourceService.getData(id), + HttpStatus.OK); + } catch (ResourceNotFoundException requestedResourceServiceException) { + LOGGER.debug( + "Could not find resource in RequestedResourceService. [id=({}), exception=({})]", + id, requestedResourceServiceException.getMessage()); + LOGGER.debug("Could not find resource. [id=({})]", id); + return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND); + } } + } catch (InvalidResourceException exception) { + LOGGER.debug("The resource could be found but was invalid. [id=({}), exception=({})]", + id, exception.getMessage()); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } catch (ContractException exception) { + LOGGER.debug("The policy cannot be enforced. [id=({}), exception=({})]", + id, exception.getMessage()); + return new ResponseEntity<>("The deposited policy cannot be enforced.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (Exception exception) { + LOGGER.warn("Failed to load resource. [id=({}), exception=({})]", id, + exception.getMessage()); + return new ResponseEntity<>("Something went wrong.", + HttpStatus.INTERNAL_SERVER_ERROR); } } /** * Gets resource data as a string by representation id. * - * @param resourceId The resource 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.") + @Operation(summary = "Request Data String by Representation", + description = "Get the resource's data as a string by representation.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error")}) @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 { + 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) { 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); + return new ResponseEntity<>( + offeredResourceService.getDataByRepresentation(resourceId, representationId), + HttpStatus.OK); + } catch (ResourceNotFoundException offeredResourceServiceException) { + LOGGER.debug( + "Could not find resource in OfferedResourceService. [id=({}), exception=({})]", + resourceId, offeredResourceServiceException.getMessage()); + + try { + return new ResponseEntity<>(requestedResourceService.getData(resourceId), + HttpStatus.OK); + } catch (ResourceNotFoundException requestedResourceServiceException) { + LOGGER.debug( + "Could not find resource in RequestedResourceService. [id=({}), exception=({})]", + resourceId, offeredResourceServiceException.getMessage()); + LOGGER.debug("Could not find resource. [id=({})]", resourceId); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } } + } catch (InvalidResourceException exception) { + LOGGER.debug("The resource could be found but was invalid. [id=({}), exception=({})]", + resourceId, exception.getMessage()); + return new ResponseEntity<>("Resource not found.", HttpStatus.NOT_FOUND); + } catch (ContractException exception) { + LOGGER.debug("The policy cannot be enforced. [id=({}), exception=({})]", + resourceId, exception.getMessage()); + return new ResponseEntity<>("The deposited policy cannot be enforced.", + HttpStatus.INTERNAL_SERVER_ERROR); + } catch (Exception exception) { + LOGGER.warn("Failed to load resource. [id=({}), exception=({})]", resourceId, + exception.getMessage()); + return new ResponseEntity<>("Something went wrong.", + HttpStatus.INTERNAL_SERVER_ERROR); } } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/ConnectorConfigurationException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/ConnectorConfigurationException.java new file mode 100644 index 000000000..3d2079059 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/ConnectorConfigurationException.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that a problem with the connector configuration exists. + */ +public class ConnectorConfigurationException extends IllegalArgumentException { + //Default serial version uid + private static final long serialVersionUID = 1L; + + /** + * Construct a ConnectorConfigurationException with the specified detail message. + * + * @param msg The detail message. + */ + public ConnectorConfigurationException(String msg) { + super(msg); + } + + /** + * Construct a ConnectorConfigurationException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public ConnectorConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/RequestFormatException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/RequestFormatException.java new file mode 100644 index 000000000..c88a78328 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/RequestFormatException.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions; + +/** + * Thrown to indicate that the request format did not match expectations. + */ +public class RequestFormatException extends IllegalArgumentException { + //Default serial version uid + private static final long serialVersionUID = 1L; + + /** + * Construct a RequestFormatException with the specified detail message. + * + * @param msg The detail message. + */ + public RequestFormatException(String msg) { + super(msg); + } + + /** + * Construct a RequestFormatException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public RequestFormatException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDCreationException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDCreationException.java new file mode 100644 index 000000000..86c8d4837 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDCreationException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.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(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDFormatException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDFormatException.java new file mode 100644 index 000000000..f54f8ac1a --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/UUIDFormatException.java @@ -0,0 +1,20 @@ +package de.fraunhofer.isst.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(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractAgreementNotFoundException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractAgreementNotFoundException.java new file mode 100644 index 000000000..5362aa6d6 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractAgreementNotFoundException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.contract; + +/** + * Thrown to indicate that no contract agreement could be found. + */ +public class ContractAgreementNotFoundException 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 ContractAgreementNotFoundException(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractException.java new file mode 100644 index 000000000..650cdd4c1 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/ContractException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.contract; + +/** + * 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(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/UnsupportedPatternException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/UnsupportedPatternException.java new file mode 100644 index 000000000..c6db284fe --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/contract/UnsupportedPatternException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.contract; + +/** + * 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(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageBuilderException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageBuilderException.java new file mode 100644 index 000000000..792c4ce2f --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageBuilderException.java @@ -0,0 +1,19 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.message; + +/** + * Thrown to indicate that the message could not be build. + */ +public class MessageBuilderException extends MessageException { + //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(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageException.java new file mode 100644 index 000000000..265b722c3 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageException.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.message; + +/** + * 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. + */ + public MessageException(String msg) { + super(msg); + } + + /** + * Construct a MessageException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public MessageException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageNotSentException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageNotSentException.java new file mode 100644 index 000000000..1b234bd82 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageNotSentException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.message; + +/** + * Thrown to indicate that the message could not be send. + */ +public class MessageNotSentException extends MessageException { + //Default serial version uid + private static final long serialVersionUID = 1L; + + /** + * Construct a MessageNotSentException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public MessageNotSentException(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageResponseException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageResponseException.java new file mode 100644 index 000000000..6e3b964cb --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/message/MessageResponseException.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.message; + +/** + * Thrown to indicate that a problem with a message response occurred. + */ +public class MessageResponseException extends MessageException { + //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(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(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/InvalidResourceException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/InvalidResourceException.java new file mode 100644 index 000000000..5b4195c9e --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/InvalidResourceException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.resource; + +/** + * 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(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/OperationNotSupportedException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/OperationNotSupportedException.java new file mode 100644 index 000000000..f326978db --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/OperationNotSupportedException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.resource; + +/** + * Thrown to indicate that this operation on resources is not supported. + */ +public class OperationNotSupportedException extends ResourceException { + //Default serial version uid + private static final long serialVersionUID = 1L; + + /** + * Construct an OperationNotSupportedException with the specified detail message and cause. + * + * @param msg The detail message. + */ + public OperationNotSupportedException(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceAlreadyExistsException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceAlreadyExistsException.java new file mode 100644 index 000000000..fbf4f9595 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceAlreadyExistsException.java @@ -0,0 +1,19 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.resource; + +/** + * Thrown to indicate that the resource already exists. + */ +public class ResourceAlreadyExistsException 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 ResourceAlreadyExistsException(String msg) { + super(msg); + } + +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceException.java new file mode 100644 index 000000000..d7d673fa5 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceException.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.resource; + +/** + * 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(String msg) { + super(msg); + } + + /** + * Construct a ResourceException with the specified detail message and cause. + * + * @param msg The detail message. + * @param cause The cause. + */ + public ResourceException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceNotFoundException.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceNotFoundException.java new file mode 100644 index 000000000..200fb1796 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/exceptions/resource/ResourceNotFoundException.java @@ -0,0 +1,18 @@ +package de.fraunhofer.isst.dataspaceconnector.exceptions.resource; + +/** + * 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(String msg) { + super(msg); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTrace.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTrace.java new file mode 100644 index 000000000..618d26f29 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTrace.java @@ -0,0 +1,28 @@ +package de.fraunhofer.isst.dataspaceconnector.filter.httptracing; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * This class stores information about a http connection + */ +@Data +@JsonInclude(Include.NON_NULL) +public class HttpTrace { + + public UUID id; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd@HH:mm:ss.SSSZ") + public LocalDateTime timestamp; + public String method; + public String url; + public String body; + public String headers; + public int status; + public String client; + public String parameterMap; +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java new file mode 100644 index 000000000..64e5e3f10 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceEventHandler.java @@ -0,0 +1,47 @@ +package de.fraunhofer.isst.dataspaceconnector.filter.httptracing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 +public class HttpTraceEventHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpTraceEventHandler.class); + private final ApplicationEventPublisher publisher; + + /** + * Constructor + * + * @param publisher The http trace event publisher + */ + HttpTraceEventHandler(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + /** + * Processes raised HttpTraceEvents + * + * @param trace The HttpTrace that needs to be processed + */ + @Async + @EventListener + public void handleHttpTraceEvent(HttpTrace trace) { + LOGGER.info("{}", trace); + } + + /** + * Raise an HttpTraceEvent. + * + * @param trace The http trace that others should be notified about. + */ + public void sendHttpTraceEvent(HttpTrace trace) { + publisher.publishEvent(trace); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceFilter.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceFilter.java new file mode 100644 index 000000000..3a4e2d5c3 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/HttpTraceFilter.java @@ -0,0 +1,140 @@ +package de.fraunhofer.isst.dataspaceconnector.filter.httptracing; + +import de.fraunhofer.isst.dataspaceconnector.filter.httptracing.internal.RequestWrapper; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; + +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.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.UUID; + + +/** + * Use this class to log all incoming and outgoing http traffic. + */ +@Component +@Order(1) +public class HttpTraceFilter extends OncePerRequestFilter { + + private UUID traceId; + private final HttpTraceEventHandler eventHandler; + + /** + * Constructor + * + * @param eventHandler The handler responsible for HttpTrace events raised by this class + */ + public HttpTraceFilter(HttpTraceEventHandler eventHandler) { + this.eventHandler = eventHandler; + } + + private static UUID generateUUID() { + return UUIDUtils.createUUID(uuid -> false); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + 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(RequestWrapper request) { + final var trace = new HttpTrace(); + trace.id = traceId; + trace.timestamp = LocalDateTime.now(); + trace.url = request.getRequestURI(); + trace.method = request.getMethod(); + trace.client = request.getRemoteAddr(); + + trace.headers = "{"; + var headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + var key = headerNames.nextElement(); + var value = request.getHeader(key); + trace.headers += key + ": " + value; + + if (headerNames.hasMoreElements()) { + trace.headers += ", "; + } + } + trace.headers += "}"; + + trace.parameterMap = "{"; + var parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + var key = parameterNames.nextElement(); + var value = request.getHeader(key); + trace.parameterMap += key + ": " + value; + + if (parameterNames.hasMoreElements()) { + trace.parameterMap += ", "; + } + } + trace.parameterMap += "}"; + + try{ + trace.body = new String(request.getRequestBody(), StandardCharsets.UTF_8); + }catch(IOException exception){ + exception.printStackTrace(); + } + + eventHandler.sendHttpTraceEvent(trace); + } + + private void afterRequest( + ContentCachingResponseWrapper responseWrapper) { + final var trace = new HttpTrace(); + trace.id = traceId; + trace.timestamp = LocalDateTime.now(); + trace.status = responseWrapper.getStatus(); + trace.body = getResponseAsPayload(responseWrapper); + + trace.headers = "{"; + var headerNames = responseWrapper.getHeaderNames(); + for (var key : headerNames) { + var value = responseWrapper.getHeader(key); + trace.headers += key + ": " + value; + + if (key != headerNames.toArray()[headerNames.toArray().length - 1]) { + trace.headers += ", "; + } + } + trace.headers += "}"; + + eventHandler.sendHttpTraceEvent(trace); + } + + private String getResponseAsPayload(ContentCachingResponseWrapper wrappedResponse) { + try { + if (wrappedResponse.getContentSize() <= 0) { + return ""; + } + + return new String(wrappedResponse.getContentAsByteArray(), 0, + wrappedResponse.getContentSize(), + wrappedResponse.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + return "ERROR"; + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java new file mode 100644 index 000000000..0a16e0874 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/filter/httptracing/internal/RequestWrapper.java @@ -0,0 +1,93 @@ +package de.fraunhofer.isst.dataspaceconnector.filter.httptracing.internal; + +import com.google.common.primitives.Bytes; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; + +/** + * Use this class to wrap incoming HTTP requests too read the message payload multiple times. + */ +public class RequestWrapper extends HttpServletRequestWrapper { + + private byte[] requestBody = new byte[0]; + private boolean isBufferFilled = false; + + /** + * The Constructor + * + * @param request The request to be wrapped + */ + public RequestWrapper(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 { + if (isBufferFilled) { + return Arrays.copyOf(requestBody, requestBody.length); + } + + var inputStream = super.getInputStream(); + if(inputStream != null){ + var buffer = new byte[128]; + var bytesRead = 0; + while ((bytesRead = inputStream.read(buffer)) != -1) { + requestBody = Bytes.concat(requestBody, Arrays.copyOfRange(buffer, 0, bytesRead)); + } + + 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()); + } + + private static class CustomServletInputStream extends ServletInputStream { + private final ByteArrayInputStream buffer; + + public CustomServletInputStream(byte[] contents) { + this.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(ReadListener listener) { + throw new RuntimeException("Not implemented"); + } + } +} 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 index 4da259517..3dedc6b2f 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/BackendSource.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/BackendSource.java @@ -1,125 +1,85 @@ package de.fraunhofer.isst.dataspaceconnector.model; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; import java.io.Serializable; import java.net.URI; /** - *

BackendSource class.

- * - * @author Julia Pampus - * @version $Id: $Id + * This class is used to describe the resource source in the backend. */ @Schema( - name = "BackendSource", - description = "Information of the backend system.", - oneOf = BackendSource.class + name = "BackendSource", + description = "Information of the backend system.", + oneOf = BackendSource.class ) +@Data +@JsonInclude(Include.NON_NULL) public class BackendSource implements Serializable { + //Default serial version uid + private static final long serialVersionUID = 1L; + + @JsonProperty("type") + private Type type; @JsonProperty("url") private URI url; - @JsonProperty("username") private String username; - @JsonProperty("password") private String password; - @JsonProperty("system") - private String system; - /** - *

Constructor for BackendSource.

+ * Constructor for BackendSource. */ public BackendSource() { } /** - *

Constructor for BackendSource.

+ * Constructor with parameters 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. + * @param type The backend type + * @param url The access url of the backend + * @param username The username for authentication + * @param password The password for authentication */ - public BackendSource(URI url, String username, String password, String system) { + public BackendSource(Type type, URI url, String username, String password) { + this.type = type; 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. + * This enum is used to describe how the backend is accessed. */ - 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; + @Schema( + name = "Type", + description = "Information of the backend system.", + oneOf = Type.class + ) + public enum Type { + @JsonProperty("local") + LOCAL("local"), + @JsonProperty("http-get") + HTTP_GET("http-get"), + @JsonProperty("https-get") + HTTPS_GET("https-get"), + @JsonProperty("https-get-basicauth") + HTTPS_GET_BASICAUTH("https-get-basicauth"); + + private final String type; + + Type(String string) { + type = string; + } + + @Override + public String toString() { + return type; + } } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java index 2664098b1..5c0a37461 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ConnectorResource.java @@ -4,79 +4,77 @@ import java.util.UUID; /** - *

ConnectorResource interface.

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

getUuid.

+ * Get the resource id * - * @return a {@link java.util.UUID} object. + * @return The id */ UUID getUuid(); /** - *

setUuid.

+ * Set the resource id * - * @param uuid a {@link java.util.UUID} object. + * @param uuid The new id */ void setUuid(UUID uuid); /** - *

getCreated.

+ * Get the creation date * - * @return a {@link java.util.Date} object. + * @return The creation date */ Date getCreated(); /** - *

setCreated.

+ * Set the creation date * - * @param created a {@link java.util.Date} object. + * @param created The creation date */ void setCreated(Date created); /** - *

getModified.

+ * Get the date of the last modification * - * @return a {@link java.util.Date} object. + * @return The modification date */ Date getModified(); /** - *

setModified.

+ * Set the date of the last modification * - * @param modified a {@link java.util.Date} object. + * @param modified The modification date */ void setModified(Date modified); /** - *

getResourceMetadata.

+ * Get the metadata associated with this resource * - * @return a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. + * @return The metadata */ ResourceMetadata getResourceMetadata(); /** - *

setResourceMetadata.

+ * Set the metadata associated with this resource * - * @param resourceMetadata a {@link de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata} object. + * @param resourceMetadata The metadata */ void setResourceMetadata(ResourceMetadata resourceMetadata); /** - *

getData.

+ * Get the data associated with this resource * - * @return a {@link java.lang.String} object. + * @return The data */ String getData(); /** - *

setData.

+ * Set the metadata associated with this resource * - * @param data a {@link java.lang.String} object. + * @param data The data */ 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 index 4bebe91d9..ca48be2a5 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/OfferedResource.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/OfferedResource.java @@ -3,21 +3,22 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; 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 + * This class describes an offered resource. */ @Data @Entity @Table -public class OfferedResource implements ConnectorResource{ +public class OfferedResource implements ConnectorResource { + @Id @JsonProperty("uuid") private UUID uuid; @@ -38,22 +39,22 @@ public class OfferedResource implements ConnectorResource{ private String data; /** - *

Constructor for OfferedResource.

+ * Constructor for OfferedResource. */ public OfferedResource() { } /** - *

Constructor for OfferedResource.

+ * Constructor with parameters 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. + * @param created The resource creation date + * @param modified The date when the resource was last modified + * @param resourceMetadata The metadata associated with this resource + * @param data The data associated with this resource */ - public OfferedResource(UUID uuid, Date created, Date modified, ResourceMetadata resourceMetadata, String data) { + public OfferedResource(UUID uuid, Date created, Date modified, + ResourceMetadata resourceMetadata, String data) { this.uuid = uuid; this.created = created; this.modified = modified; @@ -61,63 +62,86 @@ public OfferedResource(UUID uuid, Date created, Date modified, ResourceMetadata this.data = data; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public UUID getUuid() { return uuid; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setUuid(UUID uuid) { this.uuid = uuid; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public Date getCreated() { return created; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setCreated(Date created) { this.created = created; + this.modified = new Date(); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public Date getModified() { return modified; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setModified(Date modified) { this.modified = modified; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public ResourceMetadata getResourceMetadata() { return resourceMetadata; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setResourceMetadata(ResourceMetadata resourceMetadata) { + this.setModified(new Date()); this.resourceMetadata = resourceMetadata; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public String getData() { return data; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setData(String data) { + this.setModified(new Date()); 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 index 5cd6a9660..4d3fba192 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/RequestedResource.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/RequestedResource.java @@ -9,15 +9,13 @@ 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 + * This class describes a resource requested. */ @Data @Entity @Table -public class RequestedResource implements ConnectorResource{ +public class RequestedResource implements ConnectorResource { + @Id @GeneratedValue @JsonProperty("uuid") @@ -42,22 +40,23 @@ public class RequestedResource implements ConnectorResource{ private Integer accessed; /** - *

Constructor for RequestedResource.

+ * Constructor for RequestedResource. */ public RequestedResource() { } /** - *

Constructor for RequestedResource.

+ * Constructor with parameters 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. + * @param created The resource creation date + * @param modified The date when the resource was last modified + * @param resourceMetadata The metadata associated with this resource + * @param data The data associated with this resource + * @param accessed The number of times the data was accessed */ - public RequestedResource(Date created, Date modified, ResourceMetadata resourceMetadata, String data, Integer accessed) { + public RequestedResource(Date created, Date modified, ResourceMetadata resourceMetadata, + String data, Integer accessed) { this.created = created; this.modified = modified; this.resourceMetadata = resourceMetadata; @@ -65,81 +64,97 @@ public RequestedResource(Date created, Date modified, ResourceMetadata resourceM this.accessed = accessed; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public UUID getUuid() { return uuid; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setUuid(UUID uuid) { this.uuid = uuid; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public Date getCreated() { return created; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setCreated(Date created) { this.created = created; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public Date getModified() { return modified; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setModified(Date modified) { this.modified = modified; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public ResourceMetadata getResourceMetadata() { return resourceMetadata; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setResourceMetadata(ResourceMetadata resourceMetadata) { this.resourceMetadata = resourceMetadata; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public String getData() { + incrementDataAccess(); return data; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void setData(String data) { this.data = data; } /** - *

Getter for the field accessed.

+ * Describes how often the data has been accessed * - * @return a {@link java.lang.Integer} object. + * @return The number of times the data has been accessed */ 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; + private void incrementDataAccess() { + this.accessed++; } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceContract.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceContract.java new file mode 100644 index 000000000..054db8bb6 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceContract.java @@ -0,0 +1,43 @@ +package de.fraunhofer.isst.dataspaceconnector.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.UUID; + +/** + * This class provides a model to handle agreed resource contracts. + */ +@Data +@Entity +@Table +public class ResourceContract { + @Id + @JsonProperty("uuid") + private UUID id; + + @JsonProperty("contract") + @Column(columnDefinition = "TEXT") + private String contract; + + /** + * Constructor for ResourceContract. + */ + public ResourceContract() { + } + + /** + * Constructor for ResourceContract. + * + * @param id The id + * @param contract The contract's text. + */ + public ResourceContract(UUID id, String contract) { + this.id = id; + this.contract = contract; + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java index ed65de1f1..9f1a81380 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceMetadata.java @@ -1,41 +1,45 @@ package de.fraunhofer.isst.dataspaceconnector.model; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.validation.constraints.NotNull; +import java.io.IOException; import java.io.Serializable; import java.net.URI; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 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" + name = "ResourceMetadata", + description = "Metadata of a resource", + oneOf = ResourceMetadata.class, + example = "{\"title\":\"ExampleResource\",\"description\":\"ExampleResourceDescription\",\"policy\":\"Example policy\",\"representations\":[{\"uuid\":\"8e3a5056-1e46-42e1-a1c3-37aa08b2aedd\",\"type\":\"XML\",\"byteSize\":101,\"name\":\"Example Representation\",\"source\":{\"type\":\"local\"}}]}" ) +@Data +@JsonInclude(Include.NON_NULL) public class ResourceMetadata implements Serializable { + //Default serial version uid + private static final long serialVersionUID = 1L; + @JsonProperty("title") private String title; @@ -63,184 +67,41 @@ public class ResourceMetadata implements Serializable { @ElementCollection @Column(columnDefinition = "BYTEA") @JsonProperty("representations") - private List representations; + @JsonSerialize(using = RepresentationsToJson.class) + @JsonDeserialize(using = JsonToRepresentation.class) + private Map representations; /** - *

Constructor for ResourceMetadata.

+ * Constructor for ResourceMetadata. */ public ResourceMetadata() { } /** - *

Constructor for ResourceMetadata.

+ * Constructor with parameters 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. + * @param title The title of the resource + * @param description The description of the resource + * @param keywords Keywords associated with the resource + * @param policy The policy applied to the resource + * @param owner The owner of this resource + * @param license The licence under which this resource is publised + * @param version The version of the resource + * @param representations The representations of the resource */ 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) { + URI owner, URI license, String version, Map representations) { 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(); @@ -252,4 +113,51 @@ public String toString() { } return jsonString; } + + private static class RepresentationsToJson extends + JsonSerializer> { + + @Override + public void serialize(Map value, JsonGenerator gen, + SerializerProvider provider) { + try { + gen.writeObject(value.values()); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + } + + private static class JsonToRepresentation extends + JsonDeserializer> { + + @Override + public Map deserialize(JsonParser p, + DeserializationContext ctx) { + try { + var node = p.readValueAsTree(); + final var objectMapper = new ObjectMapper(); + + var representations = IntStream.range(0, node.size()).boxed() + .map(i -> { + try { + return objectMapper + .readValue(node.get(i).toString(), ResourceRepresentation.class); + } catch (IOException e) { + throw new RuntimeException(); + } + }).collect(Collectors.toList()); + + var output = new HashMap(); + for (var representation : representations) { + output.put(representation.getUuid(), representation); + } + + return output; + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } + } + } } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java index af2115257..9a9d8fcea 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/model/ResourceRepresentation.java @@ -1,7 +1,12 @@ package de.fraunhofer.isst.dataspaceconnector.model; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; 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 lombok.Data; import javax.persistence.Column; import javax.persistence.Id; @@ -9,58 +14,19 @@ import java.util.UUID; /** - *

ResourceRepresentation class.

- * - * @author Julia Pampus - * @version $Id: $Id + * The class is used for describing a representation of a resource. */ @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" + - " }" -) + name = "ResourceRepresentation", + description = "Representation of a resource", + oneOf = ResourceRepresentation.class, + example = + "{\"uuid\":\"55795317-0aaa-4fe1-b336-b2e26a00597f\",\"type\":\"JSON\",\"byteSize\":101,\"name\":\"Example Representation\",\"source\":{\"type\":\"http-get\",\"url\":\"https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=439d4b804bc8187953eb36d2a8c26a02\"}}") +@Data +@JsonInclude(Include.NON_NULL) 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; - } - } + //Default serial version uid + private static final long serialVersionUID = 1L; @Id @JsonProperty("uuid") @@ -72,123 +38,46 @@ public String toString() { @JsonProperty("byteSize") private Integer byteSize; - @JsonProperty("sourceType") - private SourceType sourceType; + @JsonProperty("name") + private String name; @JsonProperty("source") @Column(columnDefinition = "BLOB") private BackendSource source; /** - *

Constructor for ResourceRepresentation.

+ * Constructor for ResourceRepresentation. */ public ResourceRepresentation() { } /** - *

Constructor for ResourceRepresentation.

+ * Constructor with parameters 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. + * @param uuid The id + * @param type The resource type + * @param byteSize The resource size + * @param name The resource name + * @param source The backend source information */ - public ResourceRepresentation(UUID uuid, String type, Integer byteSize, SourceType sourceType, BackendSource source) { + public ResourceRepresentation(UUID uuid, String type, Integer byteSize, String name, + BackendSource source) { this.uuid = uuid; this.type = type; this.byteSize = byteSize; - this.sourceType = sourceType; + this.name = name; 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; + @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/repositories/ContractAgreementRepository.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/ContractAgreementRepository.java new file mode 100644 index 000000000..9aefddc81 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/ContractAgreementRepository.java @@ -0,0 +1,14 @@ +package de.fraunhofer.isst.dataspaceconnector.repositories; + +import de.fraunhofer.isst.dataspaceconnector.model.ResourceContract; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +/** + * Interface to the repository containing the contact agreements. + */ +@Repository +public interface ContractAgreementRepository extends JpaRepository { +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/OfferedResourceRepository.java similarity index 66% rename from src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java rename to src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/OfferedResourceRepository.java index ef3b57abc..e7b807613 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/OfferedResourceRepository.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/OfferedResourceRepository.java @@ -1,4 +1,4 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; +package de.fraunhofer.isst.dataspaceconnector.repositories; import de.fraunhofer.isst.dataspaceconnector.model.OfferedResource; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,11 +7,9 @@ import java.util.UUID; /** - *

OfferedResourceRepository interface.

- * - * @author Julia Pampus - * @version $Id: $Id + * Interface to the repository containing the offered resources. */ @Repository public interface OfferedResourceRepository extends JpaRepository { + } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/RequestedResourceRepository.java similarity index 66% rename from src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java rename to src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/RequestedResourceRepository.java index 4ce8335c6..db80fa0d2 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resource/RequestedResourceRepository.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/repositories/RequestedResourceRepository.java @@ -1,4 +1,4 @@ -package de.fraunhofer.isst.dataspaceconnector.services.resource; +package de.fraunhofer.isst.dataspaceconnector.repositories; import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,11 +7,9 @@ import java.util.UUID; /** - *

RequestedResourceRepository interface.

- * - * @author Julia Pampus - * @version $Id: $Id + * Interface to the repository containing the requested resources. */ @Repository public interface RequestedResourceRepository extends JpaRepository { + } diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/ExampleResources.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/ExampleResources.java new file mode 100644 index 000000000..287c36fd5 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/ExampleResources.java @@ -0,0 +1,95 @@ +package de.fraunhofer.isst.dataspaceconnector.services; + +import de.fraunhofer.isst.dataspaceconnector.model.BackendSource; +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.utils.UUIDUtils; + +import java.util.Collections; +import java.util.Date; +import java.util.UUID; + +/** + * Creates example resources. + */ +public class ExampleResources { + + /** + * Creates an example resource. + * @return example resource. + */ + @SuppressWarnings("unused") + public static OfferedResource getExampleResource() { + return new OfferedResource( + UUIDUtils.createUUID((UUID x) -> false), + new Date(), + new Date(), + getExampleMetadata(), + getExampleData()); + } + + /** + * Creates example metadata. + * @return example metadata. + */ + public static ResourceMetadata getExampleMetadata() { + final var metadata = new ResourceMetadata(); + metadata.setRepresentations( + Collections.singletonMap( + UUIDUtils.createUUID((UUID x) -> false), getExampleResourceRepresentation())); + metadata.setDescription("ExampleResourceDescription"); + metadata.setTitle("ExampleResource"); + metadata.setPolicy(getExamplePolicy()); + + return metadata; + } + + /** + * Creates an example policy. + * @return example policy. + */ + @SuppressWarnings("SameReturnValue") + public static String getExamplePolicy() { + return "Example policy"; + } + + /** + * Creates example data. + * @return example data. + */ + public static String getExampleData() { + return "\n" + + "Everyone\n" + + "Me\n" + + "Reminder\n" + + "Don't call!\n" + + ""; + } + + /** + * Creates an example representation. + * @return example representation. + */ + public static ResourceRepresentation getExampleResourceRepresentation() { + final var representation = new ResourceRepresentation(); + representation.setUuid(UUIDUtils.createUUID((UUID x) -> false)); + representation.setSource(getExampleBackendSource()); + representation.setName("Example Representation"); + representation.setByteSize(getExampleData().getBytes().length); + representation.setType("XML"); + + return representation; + } + + /** + * Creates an example backend source. + * @return example backend source. + */ + public static BackendSource getExampleBackendSource() { + final var source = new BackendSource(); + source.setType(BackendSource.Type.LOCAL); + + return 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/messages/MessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/MessageService.java new file mode 100644 index 000000000..80ff3b957 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/MessageService.java @@ -0,0 +1,285 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.isst.dataspaceconnector.exceptions.ConnectorConfigurationException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageNotSentException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageResponseException; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.communication.http.InfomodelMessageBuilder; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.ClaimsException; +import okhttp3.MultipartBody; +import org.apache.commons.fileupload.FileUploadException; +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.util.Map; +import java.util.UUID; + +/** + * Abstract class for building and sending IDS messages. + */ +@Service +public abstract class MessageService { + + private final Logger LOGGER = LoggerFactory.getLogger(MessageService.class); + + private final IDSHttpService idsHttpService; + private final ResourceService resourceService; + private final SerializerProvider serializerProvider; + private final IdsUtils idsUtils; + + /** + * Constructor for MessageService. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public MessageService(IDSHttpService idsHttpService, IdsUtils idsUtils, + SerializerProvider serializerProvider, OfferedResourceServiceImpl resourceService) + throws IllegalArgumentException { + if (idsHttpService == null) + throw new IllegalArgumentException("The IDSHttpService cannot be null."); + + if (resourceService == null) + throw new IllegalArgumentException("The OfferedResourceServiceImpl cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + + if (idsUtils == null) + throw new IllegalArgumentException("The IdsUtils cannot be null."); + + this.idsHttpService = idsHttpService; + this.resourceService = resourceService; + this.serializerProvider = serializerProvider; + this.idsUtils = idsUtils; + } + + /** + * Build an IDS message as request header. + * + * @return the message. + * @throws MessageException if the message could not be created. + */ + public abstract Message buildRequestHeader() throws MessageException; + + /** + * Build an IDS message as response header. + * + * @return the message. + * @throws MessageException if the message could not be created. + */ + public abstract Message buildResponseHeader() throws MessageException; + + /** + * Returns the recipient. + * @return the recipient. + */ + public abstract URI getRecipient(); + + /** + * Returns the serializer provider. + * @return the serializer provider. + */ + public SerializerProvider getSerializerProvider() { + return serializerProvider; + } + + /** + * Sends an IDS message with header and payload using the IDS Framework. + * + * @param payload the message payload. + * @return the HTTP response. + * @throws MessageException if a header could not be built or the message could not be sent. + */ + public Map sendMessage(String payload) throws MessageException { + Message message; + try { + message = buildRequestHeader(); + } catch (MessageBuilderException exception) { + LOGGER.warn("Message could not be built. [exception=({})]", exception.getMessage()); + throw new MessageBuilderException("Message could not be built.", exception); + } + + try { + MultipartBody body = InfomodelMessageBuilder.messageWithString(message, payload); + return idsHttpService.sendAndCheckDat(body, getRecipient()); + } catch (ClaimsException exception) { + LOGGER.warn("Invalid DAT in incoming message. [exception=({})]", exception.getMessage()); + throw new MessageResponseException("Unexpected message answer.", exception); + } catch (MessageNotSentException | FileUploadException | IOException exception) { + LOGGER.warn("Message could not be sent. [exception=({})]", exception.getMessage()); + throw new MessageBuilderException("Message could not be sent.", exception); + } + } + + /** + * Checks 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. + * @return true, if the outbound model version of the requsting connector is supported; false otherwise + * @throws ConnectorConfigurationException if no connector configuration was found + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean versionSupported(String versionString) throws ConnectorConfigurationException { + final var connector = idsUtils.getConnector(); + + for (var version : connector.getInboundModelVersion()) { + if (version.equals(versionString)) { + return true; + } + } + return false; + } + + /** + * Finds resource by a given artifact ID. + * + * @param artifactId ID of the artifact + * @return the resource + */ + public Resource findResourceFromArtifactId(UUID artifactId) { + for (var resource : resourceService.getResources()) { + for (var representation : resource.getRepresentation()) { + final var representationId = UUIDUtils.uuidFromUri(representation.getId()); + + if (representationId.equals(artifactId)) { + return resource; + } + } + } + return null; + } + + /** + * Extracts the artifact ID from contract request. + * + * @param request the contract + * @return The artifact ID. + */ + public URI getArtifactIdFromContract(Contract request) { + final var obligations = request.getObligation(); + final var permissions = request.getPermission(); + final var prohibitions = request.getProhibition(); + + if (obligations != null && !obligations.isEmpty()) + return obligations.get(0).getTarget(); + + if (permissions != null && !permissions.isEmpty()) + return permissions.get(0).getTarget(); + + if (prohibitions != null && !prohibitions.isEmpty()) + return prohibitions.get(0).getTarget(); + + return null; + } + + /** + * Finds and returns the response type for a given IDS message header. + * @param header the header + * @return the response type or null, if no matching type was found + */ + public ResponseType getResponseType(String header) { + try { + serializerProvider.getSerializer().deserialize(header, AccessTokenResponseMessage.class); + return ResponseType.ACCESS_TOKEN_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, AppRegistrationResponseMessage.class); + return ResponseType.APP_REGISTRATION_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ArtifactResponseMessage.class); + return ResponseType.ARTIFACT_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ContractAgreementMessage.class); + return ResponseType.CONTRACT_AGREEMENT; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ContractResponseMessage.class); + return ResponseType.CONTRACT_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, DescriptionResponseMessage.class); + return ResponseType.DESCRIPTION_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, OperationResultMessage.class); + return ResponseType.OPERATION_RESULT; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ParticipantResponseMessage.class); + return ResponseType.PARTICIPANT_RESPONSE; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, RejectionMessage.class); + return ResponseType.REJECTION; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ContractRejectionMessage.class); + return ResponseType.CONTRACT_REJECTION; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, ResultMessage.class); + return ResponseType.RESULT; + } catch (IOException ignored) { } + + try { + serializerProvider.getSerializer().deserialize(header, UploadResponseMessage.class); + return ResponseType.UPLOAD_RESPONSE; + } catch (IOException ignored) { } + + return null; + } + + /** + * Enum of possible response types of IDS message headers. + */ + public enum ResponseType { + ACCESS_TOKEN_RESPONSE("ACCESS_TOKEN_RESPONSE"), + APP_REGISTRATION_RESPONSE("APP_REGISTRATION_RESPONSE"), + ARTIFACT_RESPONSE("ARTIFACT_RESPONSE"), + CONTRACT_AGREEMENT("CONTRACT_AGREEMENT"), + CONTRACT_RESPONSE("CONTRACT_RESPONSE"), + DESCRIPTION_RESPONSE("DESCRIPTION_RESPONSE"), + OPERATION_RESULT("OPERATION_RESULT"), + PARTICIPANT_RESPONSE("PARTICIPANT_RESPONSE"), + REJECTION("REJECTION"), + CONTRACT_REJECTION("CONTRACT_REJECTION"), + RESULT("RESULT"), + UPLOAD_RESPONSE("UPLOAD_RESPONSE"); + + private final String type; + + ResponseType(String string) { + type = string; + } + + @Override + public String toString() { + return type; + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/NegotiationService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/NegotiationService.java new file mode 100644 index 000000000..f2ae70f5c --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/NegotiationService.java @@ -0,0 +1,327 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.isst.dataspaceconnector.exceptions.RequestFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.UnsupportedPatternException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageNotSentException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageResponseException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.ContractMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +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.util.ArrayList; +import java.util.Map; + +/** + * Contains methods required for policy negotiation. + */ +@Service +public class NegotiationService { + + private static final Logger LOGGER = LoggerFactory.getLogger(NegotiationService.class); + + private final PolicyHandler policyHandler; + private final ContractMessageService messageService; + private final SerializerProvider serializerProvider; + private final ConfigurationContainer configurationContainer; + + private boolean status; + + /** + * Constructor for NegotiationService. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public NegotiationService(ContractMessageService contractMessageService, + PolicyHandler policyHandler, SerializerProvider serializerProvider, + ConfigurationContainer configurationContainer) + throws IllegalArgumentException { + if (contractMessageService == null) + throw new IllegalArgumentException("The ContractMessageService cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + this.messageService = contractMessageService; + this.policyHandler = policyHandler; + this.status = true; + this.serializerProvider = serializerProvider; + this.configurationContainer = configurationContainer; + } + + /** + * Deserializes a contract, adds a given artifact ID to the contract's rules and sends it as contract request + * message. + * + * @param contractAsString the contract as string. + * @param artifactId ID of the artifact. + * @return The http response. + * @throws IllegalArgumentException if the contract could not be deserialized. + */ + public ContractRequest buildContractRequest(String contractAsString, URI artifactId) + throws IllegalArgumentException { + Contract contract; + try { + // Validate contract input. + contract = policyHandler.validateContract(contractAsString); + } catch (RequestFormatException exception) { + LOGGER.warn("Could not deserialize contract. [exception=({})]", + exception.getMessage()); + throw new RequestFormatException("Malformed contract. " + exception.getMessage()); + } + + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + // Build contract request. TODO: Change to curator or maintainer? + return fillContract(artifactId, connector.getId(), + messageService.buildContractRequest(contract)); + } + + /** + * Checks if the contract request has been successful and, if so, sends a contract agreement message. + * + * @param recipient recipient of the contract agreement message. + * @param header message header. + * @param payload message payload. + * @return the contract agreement ID. + * @throws ContractException if the contract could not be read. + * @throws MessageException if the contract request message could not be sent. + */ + public URI contractAccepted(URI recipient, String header, String payload) throws ContractException, + MessageException { + if (payload != null && !payload.equals("")) { + Contract contract; + try { + // Validate received contract. + contract = policyHandler.validateContract(payload); + } catch (UnsupportedPatternException exception) { + LOGGER.warn("Could not deserialize contract. [exception=({})]", + exception.getMessage()); + throw new UnsupportedPatternException("Malformed contract. " + exception.getMessage()); + } + + Map response; + try { + // Get correlation message. + URI correlationMessage; + try { + ContractAgreementMessage message = serializerProvider.getSerializer() + .deserialize(header, ContractAgreementMessage.class); + correlationMessage = message.getCorrelationMessage(); + } catch (IOException exception) { + throw new MessageResponseException("Could not read contract agreement."); + } + + // Send ContractAgreementMessage to recipient. + messageService.setResponseParameters(recipient, correlationMessage, contract.getId()); + ContractAgreement agreement = messageService.buildContractAgreement(contract); + response = messageService.sendMessage(agreement.toRdf()); + } catch (MessageException exception) { + // Failed to send a contract agreement message. + LOGGER.warn("Could not send contract agreement message. [exception=({})]", + exception.getMessage()); + throw new MessageNotSentException("Could not send contract agreement message. " + + exception.getMessage()); + } + + if (response != null) { + return contract.getId(); + } else { + // Failed to read the contract response message. + LOGGER.info("Received invalid ids response."); + throw new MessageResponseException("Failed to read the ids response message."); + } + } else { + // Failed to read the contract response message. + LOGGER.info("Received no valid contract."); + throw new MessageResponseException("Failed to read the ids response message."); + } + } + + /** + * Returns the status. + * @return the status. + */ + public boolean isStatus() { + return status; + } + + /** + * Sets the status. + * @param status the status. + */ + public void setStatus(boolean status) { + this.status = status; + } + + /** + * Adds an artifact ID to every rule in a given contract contract. + * + * @param artifactId ID of the artifact + * @param consumer consumer of the contract request + * @param contract the contract + * @return A valid contract request. + */ + private ContractRequest fillContract(URI artifactId, URI consumer, ContractRequest contract) { + ContractRequestImpl request = (ContractRequestImpl) contract; + + final var obligations = request.getObligation(); + final var permissions = request.getPermission(); + final var prohibitions = request.getProhibition(); + + if (obligations != null && !obligations.isEmpty()) { + for (Rule r : obligations) + ((DutyImpl) r).setTarget(artifactId); + } + + if (permissions != null && !permissions.isEmpty()) { + for (Rule r : permissions) + ((PermissionImpl) r).setTarget(artifactId); + } + + if (prohibitions != null && !prohibitions.isEmpty()) { + for (Rule r : prohibitions) + ((ProhibitionImpl) r).setTarget(artifactId); + } + + // Add consumer to contract. + request.setConsumer(consumer); + return request; + } + + /** + * Compares two contracts to each other. + * + * @param request the requested contract + * @param offer the offered contract + * @return true, if the contracts are equal; false otherwise + */ + public boolean compareContracts(Contract request, Contract offer) { + if (request == null || offer == null) + return false; + + boolean permissions; + if (request.getPermission() != null && offer.getPermission() != null) { + permissions = comparePermissions(request.getPermission(), offer.getPermission()); + } else { + permissions = true; + } + + boolean prohibitions; + if (request.getProhibition() != null && offer.getProhibition() != null) { + prohibitions = compareRules(request.getProhibition(), offer.getProhibition()); + } else { + prohibitions = true; + } + + boolean obligations; + if (request.getObligation() != null && offer.getObligation() != null) { + obligations = compareRules(request.getObligation(), offer.getObligation()); + } else { + obligations = true; + } + + return permissions && prohibitions && obligations; + } + + /** + * Compares the content of two permissions lists. + * + * @param request list of requested permissions + * @param offer list of offered permissions + * @return true, if the contents are equal; false otherwise + */ + private boolean comparePermissions( + ArrayList request, ArrayList offer) { + if (request.size() != offer.size()) + return false; + + for (int i = 0; i < request.size(); i++) { + final var requestPermission = request.get(i); + final var offerPermission = offer.get(i); + + if (requestPermission.getPostDuty() != null && offerPermission.getPostDuty() != null + && requestPermission.getPostDuty().size() > 0 && offerPermission.getPostDuty().size() > 0) { + for (int j = 0; j < requestPermission.getPostDuty().size(); j++) { + if (!requestPermission.getPostDuty().get(j).toRdf() + .equals(offerPermission.getPostDuty().get(j).toRdf())) + return false; + } + } + + if (requestPermission.getPreDuty() != null && offerPermission.getPreDuty() != null + && requestPermission.getPreDuty().size() > 0 && offerPermission.getPreDuty().size() > 0) { + for (int j = 0; j < requestPermission.getPreDuty().size(); j++) { + if (!requestPermission.getPreDuty().get(j).toRdf() + .equals(offerPermission.getPreDuty().get(j).toRdf())) + return false; + } + } + + if (requestPermission.getConstraint() != null && offerPermission.getConstraint() != null + && requestPermission.getConstraint().size() > 0 && offerPermission.getConstraint().size() > 0) { + for (int j = 0; j < requestPermission.getConstraint().size(); j++) { + if (!requestPermission.getConstraint().get(j).toRdf() + .equals(offerPermission.getConstraint().get(j).toRdf())) + return false; + } + } + + if (!requestPermission.getAction().get(0).toRdf() + .equals(offerPermission.getAction().get(0).toRdf())) + return false; + } + + return true; + } + + /** + * Compares the content of two lists of prohibitions or obligations. + * + * @param request list of requested prohibitions or obligations + * @param offer list of offered prohibitions or obligations + * @return true, if the contents are equal; false otherwise + */ + private boolean compareRules( + ArrayList request, ArrayList offer) { + if (request.size() != offer.size()) + return false; + + for (int i = 0; i < request.size(); i++) { + final var requestPermission = request.get(i); + final var offerPermission = offer.get(i); + + if (requestPermission.getConstraint() != null && offerPermission.getConstraint() != null + && requestPermission.getConstraint().size() > 0 && offerPermission.getConstraint().size() > 0) { + for (int j = 0; j < requestPermission.getConstraint().size(); j++) { + if (!requestPermission.getConstraint().get(j).toRdf() + .equals(offerPermission.getConstraint().get(j).toRdf())) + return false; + } + } + + if (!requestPermission.getAction().get(0).toRdf() + .equals(offerPermission.getAction().get(0).toRdf())) + return false; + } + + return true; + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ArtifactMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ArtifactMessageHandler.java new file mode 100644 index 000000000..371796d80 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ArtifactMessageHandler.java @@ -0,0 +1,338 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.ArtifactRequestMessage; +import de.fraunhofer.iais.eis.ArtifactRequestMessageImpl; +import de.fraunhofer.iais.eis.Contract; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.RequestFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractAgreementNotFoundException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceContract; +import de.fraunhofer.isst.dataspaceconnector.services.messages.NegotiationService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.ArtifactMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ContractAgreementService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +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 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; + +/** + * This @{@link 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. + */ +@Component +@SupportedMessageType(ArtifactRequestMessageImpl.class) +public class ArtifactMessageHandler implements MessageHandler { + + public static final Logger LOGGER = LoggerFactory.getLogger(ArtifactMessageHandler.class); + + private final ResourceService resourceService; + private final PolicyHandler policyHandler; + private final ArtifactMessageService messageService; + private final NegotiationService negotiationService; + private final ContractAgreementService contractAgreementService; + private final ConfigurationContainer configurationContainer; + + /** + * Constructor for ArtifactMessageHandler. + * + * @param offeredResourceService The service for offered resources + * @param policyHandler The service for policies + * @param negotiationService The service for negotiations + * @param messageService The service for sending messages + * @param contractAgreementService The service for agreed contracts + * @param configurationContainer The container containing the configuration + * @throws IllegalArgumentException if one of the passed parameters is null + */ + @Autowired + public ArtifactMessageHandler(OfferedResourceServiceImpl offeredResourceService, + PolicyHandler policyHandler, NegotiationService negotiationService, + ArtifactMessageService messageService, + ContractAgreementService contractAgreementService, + ConfigurationContainer configurationContainer) + throws IllegalArgumentException { + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceService cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (messageService == null) + throw new IllegalArgumentException("The ArtifactMessageService cannot be null."); + + if (negotiationService == null) + throw new IllegalArgumentException("The NegotiationService cannot be null."); + + if (contractAgreementService == null) + throw new IllegalArgumentException("The ContractAgreementService cannot be null."); + + this.resourceService = offeredResourceService; + this.policyHandler = policyHandler; + this.messageService = messageService; + this.negotiationService = negotiationService; + this.contractAgreementService = contractAgreementService; + this.configurationContainer = configurationContainer; + } + + /** + * 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 requestMessage The request message + * @param messagePayload The message payload + * @return The response message + * @throws RuntimeException if the response body failed to be build. + */ + @Override + // NOTE: Make runtime exception more concrete and add ConnectorConfigurationException, ResourceTypeException + public MessageResponse handleMessage(ArtifactRequestMessageImpl requestMessage, + MessagePayload messagePayload) throws RuntimeException { + if (requestMessage == null) { + LOGGER.warn("Cannot respond when there is no request."); + throw new IllegalArgumentException("The requestMessage cannot be null."); + } + + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + // Check if version is supported. + if (!messageService.versionSupported(requestMessage.getModelVersion())) { + LOGGER.warn("Information Model version of requesting connector is not supported."); + return ErrorResponse.withDefaultHeader( + RejectionReason.VERSION_NOT_SUPPORTED, + "Information model version not supported.", + connector.getId(), connector.getOutboundModelVersion()); + } + + try { + // Find artifact and matching resource. + final var artifactId = extractArtifactIdFromRequest(requestMessage); + final var requestedResource = messageService.findResourceFromArtifactId(artifactId); + + if (requestedResource == null) { + // The resource was not found, reject and inform the requester. + LOGGER.debug("Resource could not be found. [id=({}), artifactId=({})]", + requestMessage.getId(), artifactId); + + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "An artifact with the given uuid is not known to the " + + "connector.", + connector.getId(), connector.getOutboundModelVersion()); + } + + // Check if the transferred contract matches the requested artifact. + if (!checkTransferContract(requestMessage.getTransferContract(), + requestMessage.getRequestedArtifact())) { + LOGGER.debug("Contract agreement could not be found. [id=({}), contractId=({})]", + requestMessage.getId(), requestMessage.getTransferContract()); + + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Missing transfer contract or wrong contract.", + connector.getId(), connector.getOutboundModelVersion()); + } + + try { + // Find the requested resource and its metadata. + final var resourceId = UUIDUtils.uuidFromUri(requestedResource.getId()); + final var resourceMetadata = resourceService.getMetadata(resourceId); + + try { + // Check if the policy allows data access. TODO: Change to contract agreement. (later) + if (policyHandler.onDataProvision(resourceMetadata.getPolicy())) { + String data; + try { + // Get the data from source. + data = resourceService.getDataByRepresentation(resourceId, artifactId); + } catch (ResourceNotFoundException exception) { + LOGGER.debug("Resource could not be found. " + + "[id=({}), resourceId=({}), artifactId=({}), exception=({})]", + requestMessage.getId(), resourceId, artifactId, + exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Resource not found.", connector.getId(), + connector.getOutboundModelVersion()); + } catch (InvalidResourceException exception) { + LOGGER.debug("Resource is not in a valid format. " + + "[id=({}), resourceId=({}), artifactId=({}), exception=({})]", + requestMessage.getId(), resourceId, artifactId, + exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Something went wrong.", connector.getId(), + connector.getOutboundModelVersion()); + } catch (ResourceException exception) { + LOGGER.warn("Resource could not be received. " + + "[id=({}), resourceId=({}), artifactId=({}), exception=({})]", + requestMessage.getId(), resourceId, artifactId, + exception.getMessage()); + return ErrorResponse + .withDefaultHeader(RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Something went wrong.", connector.getId(), + connector.getOutboundModelVersion()); + } + + // Build artifact response. + messageService.setResponseParameters( + requestMessage.getIssuerConnector(), + requestMessage.getTransferContract(), + requestMessage.getId()); + + return BodyResponse.create(messageService.buildResponseHeader(), data); + } else { + // The conditions for reading this resource have not been met. + LOGGER.debug("Request policy restriction detected for request." + + "[id=({}), pattern=({})]", + requestMessage.getId(), + 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 (ConstraintViolationException | MessageException exception) { + // The response could not be constructed. + throw new RuntimeException("Failed to construct the response message.", + exception); + } catch (IllegalArgumentException exception) { + LOGGER.warn("Could not deserialize contract. " + + "[id=({}), resourceId=({}), artifactId=({}), exception=({})]", + requestMessage.getId(), resourceId, artifactId, exception.getMessage()); + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Policy check failed.", + connector.getId(), connector.getOutboundModelVersion()); + } + } catch (UUIDFormatException exception) { + // The resource from the database is not identified via uuids. + LOGGER.debug( + "The resource is not valid. The uuid is not valid. [id=({}), exception=({})]", + requestMessage.getId(), exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Resource not found.", connector.getId(), + connector.getOutboundModelVersion()); + } catch (ResourceNotFoundException exception) { + // The resource could be not be found. + LOGGER.debug("The resource could not be found. [id=({}), exception=({})]", + requestMessage.getId(), exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Resource not found.", connector.getId(), + connector.getOutboundModelVersion()); + } catch (InvalidResourceException exception) { + // The resource could be not be found. + LOGGER.debug("The resource is not valid. [id=({}), exception=({})]", + requestMessage.getId(), exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Resource not found.", connector.getId(), + connector.getOutboundModelVersion()); + } + } catch (UUIDFormatException | RequestFormatException exception) { + // No resource uuid could be found in the request, reject the message. + LOGGER.debug( + "Artifact has no valid uuid. [id=({}), artifactUri=({}), exception=({})]", + requestMessage.getId(), requestMessage.getRequestedArtifact(), + exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "No valid resource id found.", + connector.getId(), + connector.getOutboundModelVersion()); + } catch (ContractAgreementNotFoundException exception) { + LOGGER.warn("Could not load contract from database. " + + "[id=({}), contractId=({}),exception=({})]", + requestMessage.getId(), requestMessage.getTransferContract(), exception.getMessage()); + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Invalid transfer contract id.", + connector.getId(), connector.getOutboundModelVersion()); + } catch (ContractException exception) { + LOGGER.warn("Could not deserialize contract. " + + "[id=({}), contractId=({}),exception=({})]", + requestMessage.getId(), requestMessage.getTransferContract(), exception.getMessage()); + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Something went wrong.", + connector.getId(), connector.getOutboundModelVersion()); + } + } + + /** + * Extract the artifact id. + * + * @param requestMessage The artifact request message + * @return The artifact id + * @throws RequestFormatException if uuid could not be extracted. + */ + private UUID extractArtifactIdFromRequest(ArtifactRequestMessage requestMessage) + throws RequestFormatException { + try { + return UUIDUtils.uuidFromUri(requestMessage.getRequestedArtifact()); + } catch (UUIDFormatException exception) { + throw new RequestFormatException( + "The uuid could not extracted from request" + requestMessage.getId(), + exception); + } + } + + /** + * Check if the transfer contract is not null and valid. + * + * @param contractId The id of the contract + * @param artifactId The id of the artifact + * @return True if everything's fine. + */ + private boolean checkTransferContract(URI contractId, URI artifactId) throws ContractException { + if (negotiationService.isStatus()) { + if (contractId == null) { + return false; + } else { + String contractToString; + try { + UUID uuid = UUIDUtils.uuidFromUri(contractId); + // Get contract agreement from database. + ResourceContract contract = contractAgreementService.getContract(uuid); + // Get contract from database entry. + contractToString = contract.getContract(); + } catch (Exception e) { + throw new ContractAgreementNotFoundException("Contract could not be loaded " + + "from database."); + } + + Contract agreement; + try { + agreement = policyHandler.validateContract(contractToString); + } catch (RequestFormatException exception) { + throw new ContractException("Could not deserialize contract."); + } + + URI extractedId = messageService.getArtifactIdFromContract(agreement); + return extractedId.equals(artifactId); + } + } else { + return true; + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ContractMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ContractMessageHandler.java new file mode 100644 index 000000000..1afa4e9d6 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/ContractMessageHandler.java @@ -0,0 +1,295 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.RequestFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceContract; +import de.fraunhofer.isst.dataspaceconnector.services.messages.NegotiationService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.ContractMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.LogMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ContractAgreementService; +import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +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 org.apache.commons.io.IOUtils; +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.nio.charset.StandardCharsets; +import java.util.UUID; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * This @{@link ContractMessageHandler} 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 +@SupportedMessageType(ContractRequestMessageImpl.class) +public class ContractMessageHandler implements MessageHandler { + + public static final Logger LOGGER = LoggerFactory.getLogger(ContractMessageHandler.class); + + private final ConfigurationContainer configurationContainer; + private final NegotiationService negotiationService; + private final PolicyHandler policyHandler; + private final ContractMessageService messageService; + private final DapsTokenProvider tokenProvider; + private final ContractAgreementService contractAgreementService; + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + private final LogMessageService logMessageService; + private RequestMessage requestMessage; + + /** + * Constructor for NotificationMessageHandler. + * + * @param configurationContainer The container with the configuration + * @param negotiationService The service with the negotation + * @param policyHandler The service for policy negotation + * @param contractAgreementService The service for the contract agreements + * @param messageService The service for sending messages + * @param logMessageService The service for logging + * @param tokenProvider The provider for token + * @throws IllegalArgumentException if one of the parameters is null. + */ + @Autowired + public ContractMessageHandler(ConfigurationContainer configurationContainer, + NegotiationService negotiationService, PolicyHandler policyHandler, + ContractAgreementService contractAgreementService, + ContractMessageService messageService, + LogMessageService logMessageService, DapsTokenProvider tokenProvider) + throws IllegalArgumentException { + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (negotiationService == null) + throw new IllegalArgumentException("The NegotiationService cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + if (contractAgreementService == null) + throw new IllegalArgumentException("The ContractAgreementService cannot be null."); + + if (messageService == null) + throw new IllegalArgumentException("The ContractRequestService cannot be null."); + + if (logMessageService == null) + throw new IllegalArgumentException("The LogMessageService cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + this.configurationContainer = configurationContainer; + this.negotiationService = negotiationService; + this.policyHandler = policyHandler; + this.contractAgreementService = contractAgreementService; + this.messageService = messageService; + this.logMessageService = logMessageService; + this.tokenProvider = tokenProvider; + } + + /** + * 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 requestMessage The received contract request message. + * @param messagePayload The message's content. + * @return The response message. + * @throws RuntimeException - if the response body failed to be build. + */ + @Override + public MessageResponse handleMessage(ContractRequestMessageImpl requestMessage, + MessagePayload messagePayload) throws RuntimeException { + if (requestMessage == null) { + LOGGER.warn("Cannot respond when there is no request."); + throw new IllegalArgumentException("The requestMessage cannot be null."); + } else { + this.requestMessage = requestMessage; + } + + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + // Check if version is supported. + if (!messageService.versionSupported(requestMessage.getModelVersion())) { + LOGGER.warn("Information Model version of requesting connector is not supported."); + return ErrorResponse.withDefaultHeader( + RejectionReason.VERSION_NOT_SUPPORTED, + "Information model version not supported.", + connector.getId(), connector.getOutboundModelVersion()); + } + + // Read message payload as string. + String payload; + try { + payload = IOUtils + .toString(messagePayload.getUnderlyingInputStream(), StandardCharsets.UTF_8); + // If request is empty, return rejection message. + if (payload.equals("")) { + LOGGER.error("Contract is missing."); + return ErrorResponse + .withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "Missing contract request.", + connector.getId(), connector.getOutboundModelVersion()); + } + } catch (IOException e) { + return ErrorResponse + .withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "Malformed payload.", + connector.getId(), connector.getOutboundModelVersion()); + } + + try { + // Check the contract content. + return checkContractRequest(payload); + } catch (RuntimeException exception) { + // Something went wrong (e.g invalid config), try to fix it at a higher level. + throw new RuntimeException("Failed to construct a resource description.", exception); + } + } + + /** + * 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. + * @return A message response to the requesting connector. + */ + public MessageResponse checkContractRequest(String payload) throws RuntimeException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + try { + // Deserialize string to contract object. + final var contractRequest = (ContractRequest) policyHandler.validateContract(payload); + + // Get artifact id from contract request. + URI artifactId = messageService.getArtifactIdFromContract(contractRequest); + // Load contract offer from metadata. + ContractOffer contractOffer = getContractOfferByArtifact(artifactId); + + // Check if the contract request has the same content as the stored contract offer. + if (negotiationService.compareContracts(contractRequest, contractOffer)) { + return acceptContract(contractRequest); + } else { + // If differences have been detected. + return rejectContract(); + } + } catch (UUIDFormatException | RequestFormatException exception) { + LOGGER.debug( + "Artifact has no valid uuid. [id=({}), artifactUri=({}), exception=({})]", + requestMessage.getId(), requestMessage.getTransferContract(), + exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "No valid resource id found.", + connector.getId(), + connector.getOutboundModelVersion()); + } catch (ResourceNotFoundException exception) { + // The resource could be not be found. + LOGGER.debug("The artifact could not be found. [id=({}), exception=({})]", + requestMessage.getId(), exception.getMessage()); + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, + "Artifact not found.", connector.getId(), + connector.getOutboundModelVersion()); + } catch (MessageBuilderException exception) { + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Response could not be constructed.", + connector.getId(), connector.getOutboundModelVersion()); + } catch (RuntimeException exception) { + return ErrorResponse.withDefaultHeader( + RejectionReason.BAD_PARAMETERS, + "Malformed contract request.", + connector.getId(), connector.getOutboundModelVersion()); + } + } + + /** + * Gets the contract offer by artifact id. + * + * @param artifactId The artifact's id + * @return The resource's contract offer. + */ + private ContractOffer getContractOfferByArtifact(URI artifactId) throws ResourceNotFoundException { + UUID uuid = UUIDUtils.uuidFromUri(artifactId); + final var resource = messageService.findResourceFromArtifactId(uuid); + if (resource == null) + throw new ResourceNotFoundException("Artifact not known."); + return resource.getContractOffer().get(0); + } + + /** + * Accept contract by building a {@link ContractAgreement} and sending it as payload with a + * {@link ContractAgreementMessage}. + * + * @param contractRequest The contract request object from the data consumer. + * @return The message response to the requesting connector. + */ + private MessageResponse acceptContract(ContractRequest contractRequest) + throws UUIDFormatException, MessageException { + + messageService.setResponseParameters( + requestMessage.getIssuerConnector(), requestMessage.getId(), null); + // Turn the accepted contract request into a contract agreement. + ContractAgreement contractAgreement = messageService.buildContractAgreement(contractRequest); + // Build message header. + Message message = messageService.buildResponseHeader(); + + // Save contract agreement to database. + UUID uuid = UUIDUtils.uuidFromUri(contractAgreement.getId()); + contractAgreementService.addContract(new ResourceContract(uuid, contractAgreement.toRdf())); + // Send ContractAgreement to the ClearingHouse. TODO: Activate Clearing House communication as soon as it accepts IM 4. +// try { +// Response response = logMessageService.sendMessage(contractAgreement.toRdf()); +// if (response == null) +// throw new MessageException("Response body is empty."); +// } catch (MessageException exception) { +// // Log if the message could not be sent to the clearing house. +// LOGGER.warn("Could not connect to clearing house. " + exception.getMessage()); +// } + + // Send response to the data consumer. + return BodyResponse.create(message, contractAgreement.toRdf()); + } + + /** + * Builds a contract rejection message with a rejection reason. + * + * @return A contract rejection message. + */ + private MessageResponse rejectContract() { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return ErrorResponse.create(new ContractRejectionMessageBuilder() + ._securityToken_(tokenProvider.getDAT()) + ._correlationMessage_(requestMessage.getId()) + ._issued_(getGregorianNow()) + ._issuerConnector_(connector.getId()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._senderAgent_(connector.getId()) + ._recipientConnector_(Util.asList(requestMessage.getIssuerConnector())) + ._rejectionReason_(RejectionReason.BAD_PARAMETERS) + ._contractRejectionReason_(new TypedLiteral("Contract not accepted.", "en")) + .build(), "Contract rejected."); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/DescriptionMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/DescriptionMessageHandler.java new file mode 100644 index 000000000..faadc615c --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/DescriptionMessageHandler.java @@ -0,0 +1,201 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.DescriptionMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; + +/** + * This @{@link 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. + */ +@Component +@SupportedMessageType(DescriptionRequestMessageImpl.class) +public class DescriptionMessageHandler implements MessageHandler { + + public static final Logger LOGGER = LoggerFactory.getLogger(DescriptionMessageHandler.class); + + private final DescriptionMessageService messageService; + private final ResourceService resourceService; + private final ConfigurationContainer configurationContainer; + + /** + * Constructor for DescriptionMessageHandler. + * + * @param configurationContainer The container with the configuration + * @param messageService The service for sending messages + * @param offeredResourceService The service for offered resources + * @throws IllegalArgumentException if one of the parameters is null. + */ + @Autowired + public DescriptionMessageHandler(ConfigurationContainer configurationContainer, + DescriptionMessageService messageService, OfferedResourceServiceImpl offeredResourceService) + throws IllegalArgumentException { + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (messageService == null) + throw new IllegalArgumentException("The DescriptionMessageService cannot be null."); + + if (offeredResourceService == null) + throw new IllegalArgumentException("The OfferedResourceServiceImpl cannot be null."); + + this.messageService = messageService; + this.resourceService = offeredResourceService; + this.configurationContainer = configurationContainer; + } + + /** + * 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 requestMessage The request message + * @param messagePayload The request message payload + * @return The message response + * @throws RuntimeException if the response body failed to be build or requestMessage is null. + */ + @Override + public MessageResponse handleMessage(DescriptionRequestMessageImpl requestMessage, + MessagePayload messagePayload) throws RuntimeException { + if (requestMessage == null) { + LOGGER.warn("Cannot respond when there is no request."); + throw new IllegalArgumentException("The requestMessage cannot be null."); + } + + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + // Check if version is supported. + if (!messageService.versionSupported(requestMessage.getModelVersion())) { + LOGGER.warn("Information Model version of requesting connector is not supported."); + return ErrorResponse.withDefaultHeader( + RejectionReason.VERSION_NOT_SUPPORTED, + "Information model version not supported.", + connector.getId(), connector.getOutboundModelVersion()); + } + + // Check if a specific resource has been requested. + if (requestMessage.getRequestedElement() != null) { + try { + return constructResourceDescription(requestMessage); + } catch (RuntimeException exception) { + // Something went wrong (e.g invalid config), try to fix it at a higher level. + throw new RuntimeException("Failed to construct a resource.", exception); + } + } else { + // No resource has been requested, return a resource catalog. + try { + return constructConnectorSelfDescription(requestMessage); + } catch (RuntimeException exception) { + // Something went wrong (e.g invalid config), try to fix it at a higher level. + throw new RuntimeException("Failed to construct a self-description.", exception); + } + } + } + + /** + * Constructs the response message for a given resource description request message. + * + * @param requestMessage The message containing the resource request. + * @return The response message to the passed request. + * @throws RuntimeException - if the response message could not be constructed. + */ + public MessageResponse constructResourceDescription(DescriptionRequestMessage requestMessage) + throws RuntimeException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + try { + // Find the requested resource. + final var resourceId = UUIDUtils.uuidFromUri(requestMessage.getRequestedElement()); + final var resource = ((OfferedResourceServiceImpl) resourceService) + .getOfferedResources().get(resourceId); + + if (resource != null) { + // If the resource has been found, send the description. + messageService.setResponseParameters(requestMessage.getIssuerConnector(), + requestMessage.getId()); + return BodyResponse.create(messageService.buildResponseHeader(), + resource.toRdf()); + } else { + // If the resource has not been found, inform and reject. + LOGGER.debug("Resource could not be found. [id=({}), resourceId=({})]", + resourceId, requestMessage.getId()); + + return ErrorResponse.withDefaultHeader(RejectionReason.NOT_FOUND, String.format( + "The resource %s could not be found.", resourceId), + connector.getId(), connector.getOutboundModelVersion()); + } + } catch (UUIDFormatException exception) { + // If no resource uuid could be found in the request, reject the message. + LOGGER.debug( + "Description has no valid uuid. [id=({}), requestedElement=({}), exception=({})].", + requestMessage.getId(), requestMessage.getRequestedElement(), + exception.getMessage()); + + return ErrorResponse.withDefaultHeader(RejectionReason.BAD_PARAMETERS, + "No valid resource id found.", + connector.getId(), connector.getOutboundModelVersion()); + } catch (ConstraintViolationException | MessageException exception) { + // The response could not be constructed. + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Response could not be constructed.", + connector.getId(), connector.getOutboundModelVersion()); + } + } + + /** + * Constructs a resource catalog description message for the connector. + * + * @param requestMessage The request message + * @return A response message containing the resource catalog of the connector. + * @throws RuntimeException - if the response message could not be constructed or + * the connector could not be serialized. + */ + public MessageResponse constructConnectorSelfDescription( + DescriptionRequestMessage requestMessage) throws RuntimeException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + try { + // Create a connector with a list of offered resources. + var connectorImpl = (BaseConnectorImpl) connector; + connectorImpl.setResourceCatalog(Util.asList(new ResourceCatalogBuilder() + ._offeredResource_(new ArrayList<>(resourceService.getResources())) + .build())); + + // Answer with the resource description. + messageService.setResponseParameters(requestMessage.getIssuerConnector(), + requestMessage.getId()); + return BodyResponse.create(messageService.buildResponseHeader(), + connectorImpl.toRdf()); + } catch (ConstraintViolationException | MessageBuilderException exception) { + // The response could not be constructed. + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Response could not be constructed.", + connector.getId(), connector.getOutboundModelVersion()); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java new file mode 100644 index 000000000..7c7a2ecf7 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/handler/NotificationMessageHandler.java @@ -0,0 +1,98 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.handler; + +import de.fraunhofer.iais.eis.NotificationMessageImpl; +import de.fraunhofer.iais.eis.RejectionReason; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.NotificationMessageService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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) +public class NotificationMessageHandler implements MessageHandler { + + public static final Logger LOGGER = LoggerFactory.getLogger(NotificationMessageHandler.class); + + private final NotificationMessageService messageService; + private final ConfigurationContainer configurationContainer; + + /** + * Constructor for NotificationMessageHandler. + * + * @param configurationContainer The container with the configuration + * @param notificationMessageService The service responsible for notifications + * @throws IllegalArgumentException if one of the parameters is null. + */ + @Autowired + public NotificationMessageHandler(ConfigurationContainer configurationContainer, + NotificationMessageService notificationMessageService, DapsTokenProvider tokenProvider) + throws IllegalArgumentException { + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (notificationMessageService == null) + throw new IllegalArgumentException("The NotificationMessageService cannot be null."); + + this.configurationContainer = configurationContainer; + this.messageService = notificationMessageService; + } + + /** + * 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 notification message. + * @param messagePayload The message notification messages content. + * @return The response message. + * @throws RuntimeException - if the response body failed to be build. + */ + @Override + public MessageResponse handleMessage(NotificationMessageImpl message, + MessagePayload messagePayload) throws RuntimeException { + if (message == null) { + LOGGER.warn("Cannot respond when there is no request."); + throw new IllegalArgumentException("The requestMessage cannot be null."); + } + + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + // Check if version is supported. + if (!messageService.versionSupported(message.getModelVersion())) { + LOGGER.warn("Information Model version of requesting connector is not supported."); + return ErrorResponse.withDefaultHeader( + RejectionReason.VERSION_NOT_SUPPORTED, + "Information model version not supported.", + connector.getId(), connector.getOutboundModelVersion()); + } + + try { + // Build response header. + messageService.setResponseParameters(message.getIssuerConnector(), message.getId()); + return BodyResponse.create(messageService.buildResponseHeader(), "Message received."); + } catch (ConstraintViolationException | MessageException exception) { + // The response could not be constructed. + return ErrorResponse.withDefaultHeader( + RejectionReason.INTERNAL_RECIPIENT_ERROR, + "Response could not be constructed.", + connector.getId(), connector.getOutboundModelVersion()); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ArtifactMessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ArtifactMessageService.java new file mode 100644 index 000000000..c837538de --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ArtifactMessageService.java @@ -0,0 +1,160 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.implementation; + +import de.fraunhofer.iais.eis.ArtifactRequestMessageBuilder; +import de.fraunhofer.iais.eis.ArtifactResponseMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.MessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.UUID; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * The service for artifact messages + */ +@Service +public class ArtifactMessageService extends MessageService { + + private final ConfigurationContainer configurationContainer; + private final DapsTokenProvider tokenProvider; + private final ResourceService resourceService; + private URI recipient, artifactId, contractId, correlationMessageId; + + /** + * Constructor + * + * @param configurationContainer The container with the configuration + * @param tokenProvider The service for providing tokens + * @param idsHttpService The service for ids messaging + * @param resourceService The service for resources + * @param idsUtils The utilities for ids messages + * @param requestedResourceService The service for requested resources + * @param serializerProvider The service for serializing + * @throws IllegalArgumentException if any of the parameters is null + */ + @Autowired + public ArtifactMessageService(ConfigurationContainer configurationContainer, + DapsTokenProvider tokenProvider, IDSHttpService idsHttpService, + OfferedResourceServiceImpl resourceService, IdsUtils idsUtils, + RequestedResourceServiceImpl requestedResourceService, + SerializerProvider serializerProvider) throws IllegalArgumentException { + super(idsHttpService, idsUtils, serializerProvider, resourceService); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The ResourceService cannot be null."); + + this.configurationContainer = configurationContainer; + this.tokenProvider = tokenProvider; + this.resourceService = requestedResourceService; + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildRequestHeader() throws MessageBuilderException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new ArtifactRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._requestedArtifact_(artifactId) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildResponseHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new ArtifactResponseMessageBuilder() + ._securityToken_(tokenProvider.getDAT()) + ._correlationMessage_(correlationMessageId) + ._issued_(getGregorianNow()) + ._issuerConnector_(connector.getId()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._senderAgent_(connector.getId()) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public URI getRecipient() { + return recipient; + } + + /** + * Set the request parameters for the artifact message + * + * @param recipient The recipient of the request + * @param artifactId The id of the artifact + * @param contractId The id of the contract + */ + public void setRequestParameters(URI recipient, URI artifactId, URI contractId) { + this.recipient = recipient; + this.artifactId = artifactId; + this.contractId = contractId; + } + + /** + * Set the response parameters for the artifact message + * + * @param recipient The recipient of the response + * @param contractId The id of the contract + * @param correlationMessageId The id of the request + */ + public void setResponseParameters(URI recipient, URI contractId, URI correlationMessageId) { + this.recipient = recipient; + this.contractId = contractId; + this.correlationMessageId = correlationMessageId; + } + + /** + * Saves the data string to the internal database. + * + * @param response The data resource as string. + * @param resourceId The resource uuid. + * @throws ResourceException if any. + */ + public void saveData(String response, UUID resourceId) throws ResourceException { + try { + resourceService.addData(resourceId, response); + } catch (Exception e) { + throw new ResourceException("Data could not be saved. " + e.getMessage()); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ContractMessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ContractMessageService.java new file mode 100644 index 000000000..a286abf7e --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/ContractMessageService.java @@ -0,0 +1,181 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.implementation; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.MessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URI; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * The service for contract messages + */ +@Service +public class ContractMessageService extends MessageService { + + private final ConfigurationContainer configurationContainer; + private final DapsTokenProvider tokenProvider; + private URI recipient, contractId, correlationMessage; + + /** + * Constructor + * + * @param tokenProvider The service for providing tokens + * @param idsHttpService The service for ids messaging + * @param resourceService The service for resources + * @param configurationContainer The container with the configuration + * @param idsUtils The utilities for ids messages + * @param serializerProvider The service for serializing + * @throws IllegalArgumentException if any of the parameters is null + */ + @Autowired + public ContractMessageService(DapsTokenProvider tokenProvider, IDSHttpService idsHttpService, + OfferedResourceServiceImpl resourceService, ConfigurationContainer configurationContainer, + IdsUtils idsUtils, SerializerProvider serializerProvider) throws IllegalArgumentException { + super(idsHttpService, idsUtils, serializerProvider, resourceService); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + this.configurationContainer = configurationContainer; + this.tokenProvider = tokenProvider; + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildRequestHeader() throws MessageBuilderException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new ContractRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + ._transferContract_(contractId) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildResponseHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new ContractAgreementMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + ._correlationMessage_(correlationMessage) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public URI getRecipient() { + return recipient; + } + + /** + * Set the request parameters for the message + * + * @param recipient The recipient of the request + * @param contractId The id of the contract + */ + public void setRequestParameters(URI recipient, URI contractId) { + this.recipient = recipient; + this.contractId = contractId; + } + + /** + * Set the response parameters for the message + * + * @param recipient The recipient of the response + * @param correlationMessage The correlation message + * @param contractId The id of the contract + */ + public void setResponseParameters(URI recipient, URI correlationMessage, URI contractId) { + this.recipient = recipient; + this.correlationMessage = correlationMessage; + this.contractId = contractId; + } + + /** + * Build the contract request + * + * @param contract The contract + * @return The contract request + */ + public ContractRequest buildContractRequest(Contract contract) { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new ContractRequestBuilder() + ._consumer_(connector.getMaintainer()) + ._provider_(contract.getProvider()) + ._contractDate_(getGregorianNow()) + ._contractStart_(getGregorianNow()) + ._obligation_(contract.getObligation()) + ._permission_(contract.getPermission()) + ._prohibition_(contract.getProhibition()) + ._provider_(contract.getProvider()) + .build(); + } + + /** + * Build contract agreement. Keeps parameters and id. + * + * @param contract The contract + * @return The contract agreement + */ + public ContractAgreement buildContractAgreement(Contract contract) { + if (contractId == null) { + return new ContractAgreementBuilder() + ._consumer_(contract.getConsumer()) + ._provider_(contract.getProvider()) + ._contractDate_(contract.getContractDate()) + ._contractStart_(contract.getContractStart()) + ._obligation_(contract.getObligation()) + ._permission_(contract.getPermission()) + ._prohibition_(contract.getProhibition()) + ._provider_(contract.getProvider()) + .build(); + } else { + return new ContractAgreementBuilder(contractId) + ._consumer_(contract.getConsumer()) + ._provider_(contract.getProvider()) + ._contractDate_(contract.getContractDate()) + ._contractStart_(contract.getContractStart()) + ._obligation_(contract.getObligation()) + ._permission_(contract.getPermission()) + ._prohibition_(contract.getProhibition()) + ._provider_(contract.getProvider()) + .build(); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/DescriptionMessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/DescriptionMessageService.java new file mode 100644 index 000000000..2dfb82ad2 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/DescriptionMessageService.java @@ -0,0 +1,284 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.implementation; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageBuilderException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +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.messages.MessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * The service for description messages + */ +@Service +public class DescriptionMessageService extends MessageService { + + private final Logger LOGGER = LoggerFactory.getLogger(DescriptionMessageService.class); + + private final ConfigurationContainer configurationContainer; + private final DapsTokenProvider tokenProvider; + private final ResourceService resourceService; + private URI recipient, resourceId, correlationMessageId; + + /** + * Constructor + * + * @param tokenProvider The service for providing tokens + * @param idsHttpService The service for ids messaging + * @param resourceService The service for resources + * @param configurationContainer The container with the configuration + * @param idsUtils The utilities for ids messages + * @param serializerProvider The service for serializing + * @param requestedResourceService The service for requested resources + * @throws IllegalArgumentException if any of the parameters is null + */ + @Autowired + public DescriptionMessageService(DapsTokenProvider tokenProvider, IDSHttpService idsHttpService, + ConfigurationContainer configurationContainer, OfferedResourceServiceImpl resourceService, + IdsUtils idsUtils, SerializerProvider serializerProvider, + RequestedResourceServiceImpl requestedResourceService) throws IllegalArgumentException { + super(idsHttpService, idsUtils, serializerProvider, resourceService); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The ResourceService cannot be null."); + + this.configurationContainer = configurationContainer; + this.tokenProvider = tokenProvider; + this.resourceService = requestedResourceService; + } + + /** + * {@inheritDoc} + */ + @Override + public RequestMessage buildRequestHeader() throws MessageBuilderException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + if (resourceId == null) { + return new DescriptionRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } else { + return new DescriptionRequestMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._requestedElement_(resourceId) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildResponseHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new DescriptionResponseMessageBuilder() + ._securityToken_(tokenProvider.getDAT()) + ._correlationMessage_(correlationMessageId) + ._issued_(getGregorianNow()) + ._issuerConnector_(connector.getId()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._senderAgent_(connector.getId()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public URI getRecipient() { + return recipient; + } + + /** + * Set the request parameters for the message + * + * @param recipient The recipient of the request + * @param resourceId The id of the resource + */ + public void setRequestParameters(URI recipient, URI resourceId) { + this.recipient = recipient; + this.resourceId = resourceId; + } + + /** + * Set the response parameters for the artifact message + * + * @param recipient The recipient of the response + * @param correlationMessageId The id of the correlation message + */ + public void setResponseParameters(URI recipient, URI correlationMessageId) { + this.recipient = recipient; + this.correlationMessageId = correlationMessageId; + } + + /** + * Saves the metadata to the internal database. + * + * @param response The data resource as string. + * @param resourceId The id of the resource + * @return The UUID of the created resource. + * @throws ResourceException if any. + * @throws InvalidResourceException If the ids object could not be deserialized. + */ + public UUID saveMetadata(String response, URI resourceId) throws ResourceException, + InvalidResourceException { + Resource resource; + try { + resource = getSerializerProvider().getSerializer().deserialize(response, ResourceImpl.class); + } catch (Exception e) { + resource = findResource(response, resourceId); + } + + ResourceMetadata metadata; + try { + metadata = deserializeMetadata(resource); + } catch (Exception exception) { + LOGGER.info("Failed to deserialize metadata. [exception=({})]", exception.getMessage()); + throw new InvalidResourceException("Metadata could not be deserialized."); + } + + try { + return resourceService.addResource(metadata); + } catch (Exception exception) { + LOGGER.info("Failed to save metadata. [exception=({})]", exception.getMessage()); + throw new ResourceException("Metadata could not be saved to database."); + } + } + + /** + * Find a resource from a connector's resource catalog. + * + * @param payload The message payload + * @param resourceId The id of the resource + * @return The resource object. + * @throws InvalidResourceException If the payload could not be deserialized to a base connector. + */ + private Resource findResource(String payload, URI resourceId) throws InvalidResourceException { + Resource resource = null; + try { + Connector connector = getSerializerProvider().getSerializer().deserialize(payload, BaseConnector.class); + if (connector.getResourceCatalog() != null && !connector.getResourceCatalog().isEmpty()) { + for (Resource r : connector.getResourceCatalog().get(0).getOfferedResource()) { + if (r.getId().equals(resourceId)) { + resource = r; + break; + } + } + } + } catch (Exception exception) { + LOGGER.info("Failed to save metadata. [exception=({})]", exception.getMessage()); + throw new InvalidResourceException("Response could not be deserialized: " + payload); + } + return resource; + } + + /** + * Maps a received Infomodel resource to the internal metadata model. + * + * @param resource The resource + * @return the metadata object. + */ + private ResourceMetadata deserializeMetadata(Resource resource) { + var metadata = new ResourceMetadata(); + + if (resource.getKeyword() != null) { + List keywords = new ArrayList<>(); + for (var t : resource.getKeyword()) { + keywords.add(t.getValue()); + } + metadata.setKeywords(keywords); + } + + if (resource.getRepresentation() != null) { + var representations = new HashMap(); + for (Representation r : resource.getRepresentation()) { + int byteSize = 0; + String name = null; + String type = null; + if (r.getInstance() != null && !r.getInstance().isEmpty()) { + Artifact artifact = (Artifact) r.getInstance().get(0); + if (artifact.getByteSize() != null) + byteSize = artifact.getByteSize().intValue(); + if (artifact.getFileName() != null) + name = artifact.getFileName(); + if (r.getMediaType() != null) + type = r.getMediaType().getFilenameExtension(); + } + + ResourceRepresentation representation = new ResourceRepresentation( + UUIDUtils.uuidFromUri(r.getId()), type, byteSize, name, + new BackendSource(BackendSource.Type.LOCAL, null, null, null) + ); + + representations.put(representation.getUuid(), representation); + } + metadata.setRepresentations(representations); + } + + if (resource.getTitle() != null && !resource.getTitle().isEmpty()) + metadata.setTitle(resource.getTitle().get(0).getValue()); + + if (resource.getDescription() != null && !resource.getDescription().isEmpty()) + metadata.setDescription(resource.getDescription().get(0).getValue()); + + if (resource.getContractOffer() != null && !resource.getContractOffer().isEmpty()) + metadata.setPolicy(resource.getContractOffer().get(0).toRdf()); + + if (resource.getPublisher() != null) + metadata.setOwner(resource.getPublisher()); + + if (resource.getStandardLicense() != null) + metadata.setLicense(resource.getStandardLicense()); + + if (resource.getVersion() != null) + metadata.setVersion(resource.getVersion()); + + return metadata; + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/LogMessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/LogMessageService.java new file mode 100644 index 000000000..4c3be0328 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/LogMessageService.java @@ -0,0 +1,93 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.implementation; + +import de.fraunhofer.iais.eis.LogMessageBuilder; +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.MessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URI; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * The service for log messages + */ +@Service +public class LogMessageService extends MessageService { + + private final ConfigurationContainer configurationContainer; + private final DapsTokenProvider tokenProvider; + private final URI recipient; + + /** + * Constructor + * + * @param tokenProvider The service for providing tokens + * @param idsHttpService The service for ids messaging + * @param configurationContainer The container with the configuration + * @param resourceService The service for resources + * @param idsUtils The utilities for ids messages + * @param serializerProvider The service for serializing + * @throws IllegalArgumentException if any of the parameters is null + */ + @Autowired + public LogMessageService(DapsTokenProvider tokenProvider, IDSHttpService idsHttpService, + ConfigurationContainer configurationContainer, OfferedResourceServiceImpl resourceService, + IdsUtils idsUtils, SerializerProvider serializerProvider) throws IllegalArgumentException { + super(idsHttpService, idsUtils, serializerProvider, resourceService); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + this.configurationContainer = configurationContainer; + this.tokenProvider = tokenProvider; + + recipient = URI.create("https://ch-ids.aisec.fraunhofer.de/logs/messages/"); + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildRequestHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new LogMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildResponseHeader() throws MessageException { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public URI getRecipient() { + return recipient; + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/NotificationMessageService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/NotificationMessageService.java new file mode 100644 index 000000000..66e4da107 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/messages/implementation/NotificationMessageService.java @@ -0,0 +1,123 @@ +package de.fraunhofer.isst.dataspaceconnector.services.messages.implementation; + +import de.fraunhofer.iais.eis.Message; +import de.fraunhofer.iais.eis.MessageProcessedNotificationMessageBuilder; +import de.fraunhofer.iais.eis.NotificationMessageBuilder; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.message.MessageException; +import de.fraunhofer.isst.dataspaceconnector.services.messages.MessageService; +import de.fraunhofer.isst.dataspaceconnector.services.resources.OfferedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.ids.framework.communication.http.IDSHttpService; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; +import de.fraunhofer.isst.ids.framework.daps.DapsTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URI; + +import static de.fraunhofer.isst.ids.framework.util.IDSUtils.getGregorianNow; + +/** + * The service for notification messages + */ +@Service +public class NotificationMessageService extends MessageService { + + private final ConfigurationContainer configurationContainer; + private final DapsTokenProvider tokenProvider; + private URI recipient, correlationMessageId; + + /** + * Constructor + * + * @param tokenProvider The service for providing tokens + * @param idsHttpService The service for ids messaging + * @param configurationContainer The container with the configuration + * @param resourceService The service for resources + * @param idsUtils The utilities for ids messages + * @param serializerProvider The service for serializing + * @throws IllegalArgumentException if any of the parameters is null + */ + @Autowired + public NotificationMessageService(DapsTokenProvider tokenProvider, IDSHttpService idsHttpService, + ConfigurationContainer configurationContainer, OfferedResourceServiceImpl resourceService, + IdsUtils idsUtils, SerializerProvider serializerProvider) throws IllegalArgumentException { + super(idsHttpService, idsUtils, serializerProvider, resourceService); + + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (tokenProvider == null) + throw new IllegalArgumentException("The TokenProvider cannot be null."); + + this.configurationContainer = configurationContainer; + this.tokenProvider = tokenProvider; + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildRequestHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new NotificationMessageBuilder() + ._issued_(getGregorianNow()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._issuerConnector_(connector.getId()) + ._senderAgent_(connector.getId()) + ._securityToken_(tokenProvider.getDAT()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public Message buildResponseHeader() throws MessageException { + // Get a local copy of the current connector. + var connector = configurationContainer.getConnector(); + + return new MessageProcessedNotificationMessageBuilder() + ._securityToken_(tokenProvider.getDAT()) + ._correlationMessage_(correlationMessageId) + ._issued_(getGregorianNow()) + ._issuerConnector_(connector.getId()) + ._modelVersion_(connector.getOutboundModelVersion()) + ._senderAgent_(connector.getId()) + ._recipientConnector_(Util.asList(recipient)) + .build(); + } + + /** + * {@inheritDoc} + */ + @Override + public URI getRecipient() { + return recipient; + } + + /** + * Set the request parameters for the message + * + * @param recipient The recipient of the request + */ + public void setRequestParameters(URI recipient) { + this.recipient = recipient; + } + + /** + * Set the response parameters for the message + * + * @param recipient The recipient of the response + * @param correlationMessageId The id of the correlation message + */ + public void setResponseParameters(URI recipient, URI correlationMessageId) { + this.recipient = recipient; + this.correlationMessageId = correlationMessageId; + } +} 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/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/resources/ContractAgreementService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ContractAgreementService.java new file mode 100644 index 000000000..402b45f79 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ContractAgreementService.java @@ -0,0 +1,24 @@ +package de.fraunhofer.isst.dataspaceconnector.services.resources; + +import de.fraunhofer.isst.dataspaceconnector.model.ResourceContract; + +import java.util.UUID; + +/** + * ContractAgreementService interface. Contains methods for performing CRUD operations on contracts. + */ +public interface ContractAgreementService { + + /** + * Adds a contract. + * @param contract the contract. + */ + void addContract(ResourceContract contract); + + /** + * Finds a contract by ID. + * @param uuid ID of the contract. + * @return the contract. + */ + ResourceContract getContract(UUID uuid); +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ContractAgreementServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ContractAgreementServiceImpl.java new file mode 100644 index 000000000..5f1d9736d --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ContractAgreementServiceImpl.java @@ -0,0 +1,52 @@ +package de.fraunhofer.isst.dataspaceconnector.services.resources; + +import de.fraunhofer.isst.dataspaceconnector.model.ResourceContract; +import de.fraunhofer.isst.dataspaceconnector.repositories.ContractAgreementRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +/** + * This class implements all methods of {@link ContractAgreementService}. + * It provides methods for performing CRUD operations on contracts. + */ +@Service +public class ContractAgreementServiceImpl implements ContractAgreementService { + + private final ContractAgreementRepository contractAgreementRepository; + + /** + * Constructor for ContractAgreementServiceImpl. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public ContractAgreementServiceImpl(ContractAgreementRepository contractAgreementRepository) + throws IllegalArgumentException { + if (contractAgreementRepository == null) + throw new IllegalArgumentException("The ContractAgreementRepository cannot be null."); + + this.contractAgreementRepository = contractAgreementRepository; + } + + /** + * Adds a contract. + * @param contract the contract. + */ + @Override + public void addContract(ResourceContract contract) { + contractAgreementRepository.save(contract); + } + + /** + * Finds a contract by ID. + * @param uuid ID of the contract. + * @return the contract. + */ + @Override + public ResourceContract getContract(UUID uuid) { + //noinspection OptionalGetWithoutIsPresent + return contractAgreementRepository.findById(uuid).get(); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/OfferedResourceServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/OfferedResourceServiceImpl.java new file mode 100644 index 000000000..1beceb704 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/OfferedResourceServiceImpl.java @@ -0,0 +1,561 @@ +package de.fraunhofer.isst.dataspaceconnector.services.resources; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceAlreadyExistsException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; +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.repositories.OfferedResourceRepository; +import de.fraunhofer.isst.dataspaceconnector.services.utils.HttpUtils; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import org.apache.commons.lang3.NotImplementedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.MalformedURLException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * This class implements all methods of {@link ResourceService}. + * It provides methods for performing the CRUD operations for offered resources. + */ +@Service +public class OfferedResourceServiceImpl implements ResourceService { + + private static final Logger LOGGER = LoggerFactory.getLogger(OfferedResourceServiceImpl.class); + + private final OfferedResourceRepository offeredResourceRepository; + private final HttpUtils httpUtils; + private final IdsUtils idsUtils; + private final ContractOffer contractOffer; + + /** + * Constructor for OfferedResourceServiceImpl. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public OfferedResourceServiceImpl(OfferedResourceRepository offeredResourceRepository, + HttpUtils httpUtils, IdsUtils idsUtils) throws IllegalArgumentException { + if (offeredResourceRepository == null) + throw new IllegalArgumentException("The OfferedResourceRepository cannot be null."); + + if (httpUtils == null) + throw new IllegalArgumentException("The HttpUtils cannot be null."); + + if (idsUtils == null) + throw new IllegalArgumentException("The IdsUtils cannot be null."); + + 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(); + } + + /** + * Returns a list containing all offered resources as IDS information model resources. + * + * @return the list + */ + @Override + public List getResources() { + return getAllResources().parallelStream().map(idsUtils::getAsResource) + .collect(Collectors.toList()); + } + + /** + * Returns all offered resources as a map, where resources are mapped to their IDs. + * + * @return the map + */ + public Map getOfferedResources() { + return getAllResources().parallelStream().collect(Collectors + .toMap(OfferedResource::getUuid, idsUtils::getAsResource)); + } + + /** + * Saves the resource with its metadata. + * + * @param resourceMetadata the resource's metadata. + * @return the UUID of the newly created resource. + * @throws InvalidResourceException if the resource is not valid. + * @throws ResourceAlreadyExistsException - if the resource does already exists. + * @throws ResourceException - if the resource could not be created. + */ + @Override + public UUID addResource(ResourceMetadata resourceMetadata) throws ResourceException { + try { + final var uuid = UUIDUtils.createUUID((UUID x) -> { + try { + return getResource(x) != null; + } catch (InvalidResourceException exception) { + return false; + } + }); + + addResourceWithId(resourceMetadata, uuid); + return uuid; + } catch (UUIDFormatException exception) { + throw new ResourceException("Failed to create resource.", exception); + } + } + + /** + * Saves the resource with its metadata and a given ID. + * + * @param resourceMetadata the resource's metadata. + * @param uuid the ID + * @throws InvalidResourceException if the resource is not valid. + * @throws ResourceAlreadyExistsException - if the resource does already exists. + */ + public void addResourceWithId(ResourceMetadata resourceMetadata, UUID uuid) throws + InvalidResourceException, ResourceAlreadyExistsException { + if (getResource(uuid) != null) { + throw new ResourceAlreadyExistsException("The resource does already exist."); + } + + resourceMetadata.setPolicy(contractOffer.toRdf()); + final var resource = new OfferedResource(uuid, new Date(), new Date(), resourceMetadata, + ""); + + storeResource(resource); + LOGGER.debug("Added a new resource. [uuid=({}), metadata=({})]", uuid, resourceMetadata); + } + + /** + * Publishes the resource data by ID. + * + * @param resourceId ID of the resource + * @param data data as string + * @throws ResourceNotFoundException if the resource could not be found + * @throws InvalidResourceException if the resource is invalid + */ + @Override + public void addData(UUID resourceId, String data) throws InvalidResourceException, + ResourceNotFoundException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + resource.setData(data); + storeResource(resource); + LOGGER.debug("Added data to resource. [resourceId=({}), data=({})]", resourceId, data); + } + + /** + * Updates resource metadata by ID. + * + * @param resourceId ID of the resource + * @param resourceMetadata the updated metadata + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + public void updateResource(UUID resourceId, ResourceMetadata resourceMetadata) throws + InvalidResourceException, ResourceNotFoundException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + resource.setResourceMetadata(resourceMetadata); + storeResource(resource); + LOGGER.debug("Updated resource. [resourceId=({}), metadata=({})]", resourceId, + resourceMetadata); + } + + /** + * Updates resource policy by ID. + * + * @param resourceId ID of the resource + * @param policy the updated policy + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + public void updateContract(UUID resourceId, String policy) throws ResourceNotFoundException, + InvalidResourceException { + final var resourceMetadata = getMetadata(resourceId); + + // NOTE SAFETY CHECK + resourceMetadata.setPolicy(policy); + updateResource(resourceId, resourceMetadata); + LOGGER.debug("Updated contract of resource. [resourceId=({}), policy=({})]", resourceId, + policy); + } + + /** + * Deletes a resource by ID. + * + * @param resourceId ID of the resource + * @return true, if the the resource was deleted; false otherwise + */ + @Override + public boolean deleteResource(UUID resourceId) { + try { + if (getResource(resourceId) != null) { + offeredResourceRepository.deleteById(resourceId); + LOGGER.debug("Deleted resource. [resourceId=({})]", resourceId); + return true; + } + }catch(InvalidResourceException exception){ + // The resource exists, delete it + offeredResourceRepository.deleteById(resourceId); + LOGGER.debug("Deleted resource. [resourceId=({})]", resourceId); + return true; + } + + return false; + } + + /** + * Finds a resource by ID. + * + * @param resourceId ID of the resource + * @return the resource + */ + @Override + public OfferedResource getResource(UUID resourceId) throws InvalidResourceException { + final var resource = offeredResourceRepository.findById(resourceId); + + if (resource.isEmpty()) { + return null; + } else { + invalidResourceGuard(resource.get()); + return resource.get(); + } + } + + /** + * Returns all offered resources as a list. + * @return the list + */ + public List getAllResources() { + return offeredResourceRepository.findAll(); + } + + /** + * Gets resource metadata by ID. + * + * @return the metadata + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + @Override + public ResourceMetadata getMetadata(UUID resourceId) throws ResourceNotFoundException, + InvalidResourceException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + return resource.getResourceMetadata(); + } + + /** + * Returns all representations of a given resource as a map, where representations are mapped to their IDs. + * + * @param resourceId ID of the resource + * @return the map + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + public Map getAllRepresentations(UUID resourceId) throws + ResourceNotFoundException, InvalidResourceException { + return getMetadata(resourceId).getRepresentations(); + } + + /** + * Finds a representation by ID. + * + * @param resourceId ID of the resource. + * @param representationId ID of the representation + * @return the representation + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + @Override + public ResourceRepresentation getRepresentation(UUID resourceId, UUID representationId) throws + ResourceNotFoundException, InvalidResourceException { + return getAllRepresentations(resourceId).get(representationId); + } + + /** + * Retrieves resource data from the local database by ID. + * + * @param resourceId ID of the resource + * @return resource data as string + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + @Override + public String getData(UUID resourceId) throws ResourceNotFoundException, + ResourceException { + final var representations = getAllRepresentations(resourceId); + for (var representationId : representations.keySet()) { + try { + return getDataByRepresentation(resourceId, representationId); + } catch (ResourceException exception) { + // The resource is incomplete or wrong. + LOGGER.debug("Resource exception. [resourceId=({}), representationId=({}), exception=({})]", resourceId, representationId, exception); + } catch (RuntimeException exception) { + // The resource could not be received. + LOGGER.debug("Failed to get resource data. [resourceId=({}), representationId=({}), exception=({})]", resourceId, representationId, exception); + } + } + + // This code should never be reached since the representation should have at least one + // representation. + invalidResourceGuard(getResource(resourceId)); + // Add a runtime exception in case the resource valid logic changed. + throw new RuntimeException("This code should not have been reached."); + } + + /** + * Retrieves resource data from the local database or an external data source by ID. + * + * @param resourceId ID of the resource + * @param representationId ID of the represenation + * @return resource data as string + * @throws ResourceNotFoundException if the resource could not be found + * @throws ResourceException if the resource data could not be retrieved + */ + @Override + public String getDataByRepresentation(UUID resourceId, UUID representationId) throws + ResourceNotFoundException, ResourceException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + final var representation = getRepresentation(resourceId, representationId); + if (representation == null) { + throw new ResourceNotFoundException("The resource representation does not exist."); + } + + return getDataString(resource, representation); + } + + /** + * Adds a representation to a resource. + * + * @param resourceId ID of the resource + * @param representation the representation + * @return ID of the newly created representation + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + * @throws ResourceAlreadyExistsException if the representation already exists + */ + public UUID addRepresentation(UUID resourceId, ResourceRepresentation representation) throws + ResourceNotFoundException, InvalidResourceException, ResourceAlreadyExistsException { + final var uuid = UUIDUtils.createUUID( + (UUID x) -> { + try { + return getRepresentation(resourceId, x) != null; + } catch (InvalidResourceException e) { + return false; + } + }); + + return addRepresentationWithId(resourceId, representation, uuid); + } + + /** + * Adds a representation with a given ID to a resource. + * + * @param resourceId ID of the resource + * @param representation the representation + * @param representationId ID of the representation + * @return ID of the newly created representation + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + * @throws ResourceAlreadyExistsException if the representation already exists + */ + public UUID addRepresentationWithId(UUID resourceId, ResourceRepresentation representation, + UUID representationId) throws + ResourceNotFoundException, InvalidResourceException, ResourceAlreadyExistsException { + final var metaData = getMetadata(resourceId); + if (getRepresentation(resourceId, representationId) != null) { + throw new ResourceAlreadyExistsException("The representation does already exist."); + } + + representation.setUuid(representationId); + metaData.getRepresentations().put(representation.getUuid(), representation); + + updateResource(resourceId, metaData); + LOGGER.debug( + "Added representation to resource. [resourceId=({}), representationId=({}), representation=({})]", + resourceId, representationId, representation); + return representationId; + } + + /** + * Updates a representation by ID. + * + * @param resourceId ID of the resource + * @param representationId ID of the representation + * @param representation the updated representation + * @throws InvalidResourceException if the resource is invalid. + * @throws ResourceNotFoundException if the resource could not be found + */ + public void updateRepresentation(UUID resourceId, UUID representationId, + ResourceRepresentation representation) throws + ResourceNotFoundException, InvalidResourceException { + if (getRepresentation(resourceId, representationId) != null) { + representation.setUuid(representationId); + var representations = getAllRepresentations(resourceId); + representations.put(representationId, representation); + + var metadata = getMetadata(resourceId); + metadata.setRepresentations(representations); + + updateResource(resourceId, metadata); + LOGGER.debug( + "Updated representation of resource. [resourceId=({}), representationId=({}), representation=({})]", + resourceId, representationId, representation); + } else { + LOGGER.debug("Failed to update resource representation. It does not exist. [resourceId=({}), representationId=({}), representation=({})]", resourceId, representationId, representation); + throw new ResourceNotFoundException("The resource representation does not exist."); + } + } + + + /** + * Deletes a representation by ID. + * + * @param resourceId ID of the resource + * @param representationId ID of the representation + * @return true, if the the representation was deleted; false otherwise + * @throws ResourceNotFoundException if the resource could not be found. + * @throws InvalidResourceException if the resource is not valid. + */ + public boolean deleteRepresentation(UUID resourceId, UUID representationId) throws + ResourceNotFoundException, InvalidResourceException { + var representations = getAllRepresentations(resourceId); + if (representations.remove(representationId) != null) { + var metadata = getMetadata(resourceId); + metadata.setRepresentations(representations); + + updateResource(resourceId, metadata); + LOGGER.debug("Deleted resource representation. [resourceId=({}), representationId=({})]", + resourceId, representationId); + return true; + } else { + LOGGER.debug( + "Failed to delete resource representation. It does not exist. [resourceId=({}), representationId=({})]", + resourceId, representationId); + return false; + } + } + + /** + * Checks if a given offered resource is valid. + * @param resource the offered resource + * @return an optional string: empty, if the resource is valid; contains error description otherwise + */ + public Optional isValidOfferedResource(OfferedResource resource) { + if (resource == null) { + return Optional.of("The resource cannot be null."); + } + + if (resource.getResourceMetadata() == null) { + return Optional.of("The resource metadata cannot be null."); + } + + if (resource.getResourceMetadata().getRepresentations() == null) { + return Optional.of("The resource representation cannot be null."); + } + + return Optional.empty(); + } + + /** + * Validates an offered resource. + * + * @param resource the resource to be validated + * @throws InvalidResourceException if the resource is not valid. + */ + private void invalidResourceGuard(OfferedResource resource) throws InvalidResourceException { + final var error = isValidOfferedResource(resource); + if (error.isPresent()) { + LOGGER.debug("Failed resource validation. [error=({}), resource=({})]", error.get(), resource); + throw new InvalidResourceException("Not a valid resource. " + error.get()); + } + } + + /** + * Saves a resource after validating it. + * + * @throws InvalidResourceException if the resource is not valid. + */ + private void storeResource(OfferedResource resource) throws InvalidResourceException { + invalidResourceGuard(resource); + offeredResourceRepository.save(resource); + LOGGER.debug("Made resource persistent. [resource=({})]", resource); + } + + /** + * Gets resource data as string. + * + * @param resource the connector resource object. + * @param representation the representation. + * @return resource data as string + * @throws ResourceException if the resource source is not defined or source url is + * ill-formatted. + */ + private String getDataString(OfferedResource resource, ResourceRepresentation representation) + throws ResourceException { + if (representation.getSource() != null) { + try { + final var address = representation.getSource().getUrl(); + final var username = representation.getSource().getUsername(); + final var password = representation.getSource().getPassword(); + + switch (representation.getSource().getType()) { + case LOCAL: + return resource.getData(); + case HTTP_GET: + return httpUtils.sendHttpGetRequest(address.toString()); + case HTTPS_GET: + return httpUtils.sendHttpsGetRequest(address.toString()); + case HTTPS_GET_BASICAUTH: + return httpUtils + .sendHttpsGetRequestWithBasicAuth(address.toString(), username, + password); + default: + // This exception is only thrown when BackendSource.Type is expanded but this + // switch is not + throw new NotImplementedException("This type is not supported"); + } + } catch (MalformedURLException exception) { + // One of the http requests received a non url as address + LOGGER.debug("Failed to resolve the target address. The resource representation is not an url. [resource=({}), representation=({}), exception=({}))]", resource, representation, exception); + throw new ResourceException("The resource source representation is not an url.", + exception); + } catch (RuntimeException exception) { + // One of the http calls encountered problems. + LOGGER.debug("Failed to find the resource. [resource=({}), representation=({}), exception=({}))]", resource, representation, exception); + throw new ResourceException("The resource could not be found.", exception); + } + } else { + LOGGER.debug("Failed to receive the resource. The resource has no defined backend. [resource=({}), representation=({}))]", resource, representation); + throw new ResourceException("The resource has no defined backend."); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/RequestedResourceServiceImpl.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/RequestedResourceServiceImpl.java new file mode 100644 index 000000000..16f1c2a59 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/RequestedResourceServiceImpl.java @@ -0,0 +1,268 @@ +package de.fraunhofer.isst.dataspaceconnector.services.resources; + +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.ContractException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.InvalidResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.OperationNotSupportedException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceNotFoundException; +import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; +import de.fraunhofer.isst.dataspaceconnector.repositories.RequestedResourceRepository; +import de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler; +import de.fraunhofer.isst.dataspaceconnector.services.utils.IdsUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * This class implements all methods of {@link ResourceService}. + * It provides methods for performing the CRUD operations for requested resources. + */ +@Service +public class RequestedResourceServiceImpl implements ResourceService { + + public static final Logger LOGGER = LoggerFactory.getLogger(RequestedResourceServiceImpl.class); + + private final RequestedResourceRepository requestedResourceRepository; + private final IdsUtils idsUtils; + private final PolicyHandler policyHandler; + + /** + * Constructor for RequestedResourceServiceImpl. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public RequestedResourceServiceImpl(RequestedResourceRepository requestedResourceRepository, + IdsUtils idsUtils, PolicyHandler policyHandler) throws IllegalArgumentException { + if (requestedResourceRepository == null) + throw new IllegalArgumentException("The RequestedResourceRepository cannot be null."); + + if (idsUtils == null) + throw new IllegalArgumentException("The IdsUtils cannot be null."); + + if (policyHandler == null) + throw new IllegalArgumentException("The PolicyHandler cannot be null."); + + this.requestedResourceRepository = requestedResourceRepository; + this.idsUtils = idsUtils; + this.policyHandler = policyHandler; + } + + /** + * Saves the resource with its metadata. + * + * @param resourceMetadata the resource's metadata. + * @return the UUID of the newly created resource. + * @throws InvalidResourceException if the resource is not valid. + */ + @Override + public UUID addResource(ResourceMetadata resourceMetadata) throws InvalidResourceException { + final var resource = new RequestedResource(new Date(), new Date(), resourceMetadata, "", 0); + + storeResource(resource); + + LOGGER.debug("Added a new resource. [resource=({})]", resource); + return resource.getUuid(); + } + + /** + * Publishes resource data by ID. + * + * @param resourceId ID of the resource + * @param data data as string + * @throws ResourceNotFoundException if the resource could not be found + * @throws InvalidResourceException if the resource is invalid + */ + @Override + public void addData(UUID resourceId, String data) throws ResourceNotFoundException, + InvalidResourceException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + resource.setData(data); + + storeResource(resource); + LOGGER.debug("Added data to resource. [resourceId=({}), data=({})]", resourceId, data); + } + + /** + * Deletes a resource by ID. + * + * @param resourceId ID of the resource + * @return true, if the the resource was deleted; false otherwise + */ + @Override + public boolean deleteResource(UUID resourceId) { + try { + if (getResource(resourceId) != null) { + requestedResourceRepository.deleteById(resourceId); + LOGGER.debug("Deleted resource. [resourceId=({})]", resourceId); + return true; + } + } catch(InvalidResourceException exception){ + // The resource exists, delete it + requestedResourceRepository.deleteById(resourceId); + LOGGER.debug("Deleted resource. [resourceId=({})]", resourceId); + return true; + } + + return false; + } + + /** + * Gets a resource by ID. + * + * @param resourceId ID of the resource + * @return the resource + */ + @Override + public RequestedResource getResource(UUID resourceId) throws InvalidResourceException { + final var resource = requestedResourceRepository.findById(resourceId); + + if (resource.isEmpty()) { + return null; + } else { + invalidResourceGuard(resource.get()); + return resource.get(); + } + } + + public List getAllResources() { + return requestedResourceRepository.findAll(); + } + + /** + * Gets resource metadata by ID. + * + * @param resourceId ID of the resource + * @return the metadata + * @throws ResourceNotFoundException if the resource could not be found + */ + @Override + public ResourceMetadata getMetadata(UUID resourceId) throws ResourceNotFoundException, + InvalidResourceException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + return resource.getResourceMetadata(); + } + + /** + * Gets resource data by ID. + * + * @param resourceId ID of the resource + * @return the data + * @throws ResourceNotFoundException if the resource could not be found. + * @throws ResourceException if the data could not be retrieved. + * @throws ContractException if the policy could not be parsed or the policy pattern in not supported. + */ + @Override + public String getData(UUID resourceId) throws ResourceNotFoundException, + ResourceException, ContractException { + final var resource = getResource(resourceId); + if (resource == null) { + throw new ResourceNotFoundException("The resource does not exist."); + } + + if (policyHandler.onDataAccess(resource)) { + final var data = resource.getData(); + storeResource(resource); + return data; + } else { + LOGGER.debug("Failed to access the resource. The resource is policy restricted. [resourceId=({})]", resourceId); + return "Policy Restriction!"; + } + } + + /** + * Returns all requested resources as a list. + */ + @Override + public List getResources() { + return getAllResources().parallelStream().map(idsUtils::getAsResource) + .collect(Collectors.toList()); + } + + /** + * Gets data from the local database or an external data source. + * + * @param resourceId ID of the resource + * @param representationId ID of the representation + * @return resource data as string + * @throws OperationNotSupportedException always + */ + @Override + public String getDataByRepresentation(UUID resourceId, UUID representationId) throws + OperationNotSupportedException { + throw new OperationNotSupportedException("Operation not supported."); + } + + /** + * Gets a resource representation by ID. + * + * @param resourceId ID of the resource + * @param representationId ID of the representation + * @return the representation + * @throws OperationNotSupportedException always + */ + @Override + public ResourceRepresentation getRepresentation(UUID resourceId, UUID representationId) + throws OperationNotSupportedException { + throw new OperationNotSupportedException("Operation not supported."); + } + + /** + * Checks if a given requested resource is valid. + * @param resource the requested resource + * @return an optional string: empty, if the resource is valid; contains error description otherwise + */ + public Optional isValidRequestedResource(RequestedResource resource) { + if (resource == null) { + return Optional.of("The resource cannot be null."); + } + + if (resource.getResourceMetadata() == null) { + return Optional.of("The resource metadata cannot be null."); + } + + if (resource.getResourceMetadata().getRepresentations() == null) { + return Optional.of("The resource representation cannot be null."); + } + + return Optional.empty(); + } + + /** + * Validates a requested resource. + * + * @param resource the resource to be validated + * @throws InvalidResourceException if the resource is not valid. + */ + private void invalidResourceGuard(RequestedResource resource) throws InvalidResourceException { + final var error = isValidRequestedResource(resource); + if (error.isPresent()) { + LOGGER.debug("Failed resource validation. [error=({}), resource=({})]", error.get(), resource); + throw new InvalidResourceException(error.get()); + } + } + + private void storeResource(RequestedResource resource) throws InvalidResourceException { + invalidResourceGuard(resource); + requestedResourceRepository.save(resource); + LOGGER.debug("Made resource persistent. [resource=({})]", resource); + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ResourceService.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ResourceService.java new file mode 100644 index 000000000..73cd273df --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/resources/ResourceService.java @@ -0,0 +1,86 @@ +package de.fraunhofer.isst.dataspaceconnector.services.resources; + +import de.fraunhofer.iais.eis.Resource; +import de.fraunhofer.isst.dataspaceconnector.exceptions.resource.ResourceException; +import de.fraunhofer.isst.dataspaceconnector.model.ConnectorResource; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceMetadata; +import de.fraunhofer.isst.dataspaceconnector.model.ResourceRepresentation; + +import java.util.List; +import java.util.UUID; + +/** + * RequestedResourceService interface. Contains methods for performing CRUD operations on resources. + */ +public interface ResourceService { + + /** + * Adds resource. + * + * @throws ResourceException if the resource could not be added. + */ + UUID addResource(ResourceMetadata metadata) throws ResourceException; + + /** + * Adds data. + * + * @throws ResourceException if the data could not be added. + */ + void addData(UUID id, String data) throws ResourceException; + + /** + * Deletes resource. + * + * @return true if the resource could be found and be deleted. + */ + boolean deleteResource(UUID id); + + /** + * Finds resource by ID + * + * @throws ResourceException if the resource could not be found. + */ + ConnectorResource getResource(UUID id) throws ResourceException; + + /** + * Finds metadata by ID. + * + * @throws ResourceException if the metadata could not be found. + */ + ResourceMetadata getMetadata(UUID id) throws ResourceException; + + /** + * Finds data by ID. + * + * @throws ResourceException if the data could not be retrieved. + */ + String getData(UUID id) throws ResourceException; + + /** + * Returns all resources as list. + * + * @return a list of resources. + */ + List getResources(); + + /** + * Returns data by representation. + * + * @param resourceId ID of the resource. + * @param representationId ID of the representation. + * @return resource data as string. + * @throws ResourceException if the resource data could not be retrieved. + */ + String getDataByRepresentation(UUID resourceId, UUID representationId) throws ResourceException; + + /** + * Finds representation by ID. + * + * @param resourceId ID of the resource. + * @param representationId ID of the representation. + * @return a {@link ResourceRepresentation} object. + * @throws ResourceException if the representation could not be retrieved. + */ + ResourceRepresentation getRepresentation(UUID resourceId, UUID representationId) + throws ResourceException; +} 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 index 8e5da9d0c..8d31cb6d2 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyEnforcement.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyEnforcement.java @@ -5,9 +5,10 @@ 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 de.fraunhofer.isst.dataspaceconnector.repositories.RequestedResourceRepository; +import de.fraunhofer.isst.dataspaceconnector.services.resources.RequestedResourceServiceImpl; +import de.fraunhofer.isst.dataspaceconnector.services.resources.ResourceService; +import de.fraunhofer.isst.ids.framework.configuration.SerializerProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -15,49 +16,54 @@ 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 static final Logger LOGGER = LoggerFactory.getLogger(PolicyEnforcement.class); - private RequestedResourceService requestedResourceService; - private RequestedResourceRepository requestedResourceRepository; + private final PolicyVerifier policyVerifier; + private final ResourceService resourceService; + private final RequestedResourceRepository requestedResourceRepository; + private final SerializerProvider serializerProvider; - private SerializerProvider serializerProvider; - - @Autowired /** - *

Constructor for PolicyEnforcement.

+ * 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. + * @throws IllegalArgumentException if any of the parameters is null. */ - public PolicyEnforcement(PolicyVerifier policyVerifier, RequestedResourceService requestedResourceService, - RequestedResourceRepository requestedResourceRepository, SerializerProvider serializerProvider) { + @Autowired + public PolicyEnforcement(PolicyVerifier policyVerifier, + RequestedResourceServiceImpl requestedResourceService, + RequestedResourceRepository requestedResourceRepository, + SerializerProvider serializerProvider) throws IllegalArgumentException { + if (policyVerifier == null) + throw new IllegalArgumentException("The PolicyVerifier cannot be null."); + + if (requestedResourceService == null) + throw new IllegalArgumentException("The RequestedResourceServiceImpl cannot be null."); + + if (requestedResourceRepository == null) + throw new IllegalArgumentException("The RequestedResourceRepository cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + this.policyVerifier = policyVerifier; - this.requestedResourceService = requestedResourceService; + this.resourceService = requestedResourceService; this.requestedResourceRepository = requestedResourceRepository; this.serializerProvider = serializerProvider; } /** - * Checks all resources every minute. + * Periodically (every minute) calls {@link PolicyEnforcement#checkResources()}. * 1000 = 1 sec * 60 * 60 = every hour (3600000) */ @Scheduled(fixedDelay = 60000) @@ -71,16 +77,17 @@ public void schedule() { } /** - * Checks all know resources and its policies to delete them if necessary. + * Checks all known resources and their policies to delete them if necessary. * - * @throws java.text.ParseException if any. - * @throws java.io.IOException if any. + * @throws java.text.ParseException if a date from a policy cannot be parsed. + * @throws java.io.IOException if an error occurs while deserializing a contract. */ 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); + 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(); @@ -89,14 +96,14 @@ public void checkResources() throws ParseException, IOException { Action action = postDuties.get(0).getAction().get(0); if (action == Action.DELETE) { if (policyVerifier.checkForDelete(postDuties.get(0))) { - requestedResourceService.deleteResource(resource.getUuid()); + resourceService.deleteResource(resource.getUuid()); } } } } - } catch (IOException e) { - throw new IOException("The policy could not be read. Please check the policy syntax."); + 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 index 7706f0e1e..eda3c2174 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyHandler.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyHandler.java @@ -1,59 +1,79 @@ package de.fraunhofer.isst.dataspaceconnector.services.usagecontrol; import de.fraunhofer.iais.eis.*; +import de.fraunhofer.isst.dataspaceconnector.exceptions.RequestFormatException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.contract.UnsupportedPatternException; import de.fraunhofer.isst.dataspaceconnector.model.RequestedResource; -import de.fraunhofer.isst.ids.framework.spring.starter.SerializerProvider; +import de.fraunhofer.isst.ids.framework.configuration.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 + * This class provides policy pattern recognition and calls the {@link + * de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier} on data request or + * access. */ @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; + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyHandler.class); + + private static Contract contract; + private final PolicyVerifier policyVerifier; + private final SerializerProvider serializerProvider; + + private boolean ignoreUnsupportedPatterns; - @Autowired /** - *

Constructor for PolicyHandler.

+ * 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. + * @throws IllegalArgumentException if any of the parameters is null. */ - public PolicyHandler(PolicyVerifier policyVerifier, SerializerProvider serializerProvider) { + @Autowired + public PolicyHandler(PolicyVerifier policyVerifier, SerializerProvider serializerProvider) + throws IllegalArgumentException { + if (policyVerifier == null) + throw new IllegalArgumentException("The PolicyVerifier cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + this.policyVerifier = policyVerifier; this.serializerProvider = serializerProvider; + this.ignoreUnsupportedPatterns = false; } /** - * Reads the properties of an odrl policy to automatically recognize the policy pattern. + * Deserializes a contract object from a string. * - * @param policy The parsed policy object. - * @return The recognized policy pattern. - * @throws java.io.IOException if any. + * @param contract the contract as a string. + * @return the contract. + * @throws RequestFormatException if the string is not a valid contract and could thus not be deserialized. */ - public Pattern getPattern(String policy) throws IOException{ + public Contract validateContract(String contract) throws RequestFormatException { 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."); + return serializerProvider.getSerializer().deserialize(contract, Contract.class); + } catch (Exception e) { + LOGGER.debug("Policy pattern is not supported."); + throw new RequestFormatException("Contract could not be deserialized. ", e); } + } + + /** + * Reads the properties of an ODRL policy to automatically recognize the policy pattern. + * + * @param policy the policy as a string. + * @return the recognized policy pattern. + * @throws UnsupportedPatternException if no pattern could be recognized. + * @throws RequestFormatException if the string could not be deserialized. + */ + public Pattern getPattern(String policy) throws UnsupportedPatternException, + RequestFormatException { + contract = validateContract(policy); if (contract.getProhibition() != null && contract.getProhibition().get(0) != null) { return Pattern.PROHIBIT_ACCESS; @@ -78,7 +98,8 @@ public Pattern getPattern(String policy) throws IOException{ } else if (leftOperand == LeftOperand.ELAPSED_TIME) { return Pattern.DURATION_USAGE; } else { - throw new IOException("The recognized policy pattern is not supported by this connector."); + throw new UnsupportedPatternException( + "The recognized policy pattern is not supported by this connector."); } } } else { @@ -89,25 +110,29 @@ public Pattern getPattern(String policy) throws IOException{ } else if (action == Action.LOG) { return Pattern.USAGE_LOGGING; } else { - throw new IOException("The recognized policy pattern is not supported by this connector."); + throw new UnsupportedPatternException( + "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."); + throw new UnsupportedPatternException( + "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). + * Implements the policy restrictions depending on the policy pattern type on data provision (as provider). * - * @param policy The resource's usage policy. - * @return Whether the data can be accessed. - * @throws java.io.IOException if any. + * @param policy the resource's usage policy. + * @return whether the data can be provided. + * @throws UnsupportedPatternException if no pattern could be recognized. + * @throws RequestFormatException if the string could not be deserialized. */ - public boolean onDataProvision(String policy) throws IOException { + public boolean onDataProvision(String policy) throws UnsupportedPatternException, + RequestFormatException { switch (getPattern(policy)) { case PROVIDE_ACCESS: return policyVerifier.allowAccess(); @@ -122,16 +147,27 @@ public boolean onDataProvision(String policy) throws IOException { } /** - * Implements the policy restrictions depending on the policy pattern type (on data access as consumer). + * 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. + * @param dataResource the accessed resource. + * @return whether the data can be accessed. + * @throws UnsupportedPatternException if no pattern could be recognized. + * @throws RequestFormatException if the string could not be deserialized. */ - public boolean onDataAccess(RequestedResource dataResource) throws IOException { - String policy = dataResource.getResourceMetadata().getPolicy(); + public boolean onDataAccess(RequestedResource dataResource) throws UnsupportedPatternException, + RequestFormatException{ + final var policy = dataResource.getResourceMetadata().getPolicy(); + Pattern pattern; + try { + pattern = getPattern(policy); + } catch (UnsupportedPatternException exception) { + if (!ignoreUnsupportedPatterns) + throw new UnsupportedPatternException(exception.getMessage()); + else + pattern = Pattern.PROVIDE_ACCESS; + } - switch (getPattern(policy)) { + switch (pattern) { case USAGE_DURING_INTERVAL: case USAGE_UNTIL_DELETION: return policyVerifier.checkInterval(contract); @@ -148,48 +184,54 @@ public boolean onDataAccess(RequestedResource dataResource) throws IOException { } } + /** + * Returns the current value of {@link PolicyHandler#ignoreUnsupportedPatterns}. + * @return true, if unsupported patterns in policies are ignored; false otherwise. + */ + public boolean isIgnoreUnsupportedPatterns() { + return ignoreUnsupportedPatterns; + } + + /** + * Sets whether unsupported patterns in policies should be ignored. + * @param ignoreUnsupportedPatterns true, if unsupported patterns in policies should be ignored; false otherwise. + */ + public void setIgnoreUnsupportedPatterns(boolean ignoreUnsupportedPatterns) { + this.ignoreUnsupportedPatterns = ignoreUnsupportedPatterns; + } + 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") + * 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 + * 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 + * 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 + * 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") + * 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 + * 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 + * 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"); 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 index e6c03311f..285f1cc9b 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyReader.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyReader.java @@ -3,8 +3,6 @@ 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; @@ -17,23 +15,17 @@ 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 + * This class reads the content of the policy rules and returns required information to the {@link + * de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyVerifier}. */ @Component public class PolicyReader { - /** - * Constant LOGGER - */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyReader.class); /** - * Gets the access frequency of a policy. + * Gets the allowed number of accesses defined in a policy. * - * @param rule The policy rule object. - * @return The time frequency. + * @param rule the policy rule object. + * @return the number of allowed accesses. */ public Integer getMaxAccess(Rule rule) { Constraint constraint = rule.getConstraint().get(0); @@ -51,10 +43,10 @@ public Integer getMaxAccess(Rule rule) { } /** - * Gets the time interval of a policy. + * Gets the time interval defined in a policy. * - * @param rule The policy rule object. - * @return The time interval. + * @param rule the policy rule object. + * @return the time interval. */ public TimeInterval getTimeInterval(Rule rule) { TimeInterval timeInterval = new TimeInterval(); @@ -70,10 +62,10 @@ public TimeInterval getTimeInterval(Rule rule) { } /** - * Gets the log path value of a policy. + * Gets the endpoint value to send notifications to defined in a policy. * - * @param rule The policy rule object. - * @return The found value. + * @param rule the policy rule object. + * @return the endpoint value. */ public String getEndpoint(Rule rule) { Constraint constraint = rule.getConstraint().get(0); @@ -81,10 +73,10 @@ public String getEndpoint(Rule rule) { } /** - * Gets the log path value of a policy. + * Gets the PIP endpoint path value defined in a policy. * - * @param rule The policy rule object. - * @return The found value. + * @param rule the policy rule object. + * @return the pip endpoint value. */ public URI getPipEndpoint(Rule rule) { Constraint constraint = rule.getConstraint().get(0); @@ -92,11 +84,11 @@ public URI getPipEndpoint(Rule rule) { } /** - * Gets the date value of a policy. + * Gets the date value defined in a policy. * - * @param rule The policy constraint object. - * @return The date or null. - * @throws java.text.ParseException if any. + * @param rule the policy constraint object. + * @return the date or null. + * @throws java.text.ParseException if the date cannot be parsed. */ public Date getDate(Rule rule) throws ParseException { Constraint constraint = rule.getConstraint().get(0); @@ -108,11 +100,11 @@ public Date getDate(Rule rule) throws ParseException { } /** - * Gets the duration value of a policy. + * Gets the duration value defined in a policy. * - * @param rule The policy constraint object. - * @return The duration or null. - * @throws javax.xml.datatype.DatatypeConfigurationException if any. + * @param rule the policy constraint object. + * @return the duration or null. + * @throws javax.xml.datatype.DatatypeConfigurationException if the duration cannot be parsed. */ public Duration getDuration(Rule rule) throws DatatypeConfigurationException { Constraint constraint = rule.getConstraint().get(0); @@ -125,47 +117,28 @@ public Duration getDuration(Rule rule) throws DatatypeConfigurationException { } /** - *

TimeInterval class.

- * - * @author Julia Pampus - * @version $Id: $Id + * Inner class for a time interval format. */ public static class TimeInterval { - private String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private Date start; private Date end; /** - *

Constructor for TimeInterval.

+ * 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. + * Sets the start of a time interval. + * @param start string containing a date formatted as specified in {@link TimeInterval#DATE_FORMAT_PATTERN} */ public void setStart(String start) { try { @@ -175,19 +148,13 @@ public void setStart(String start) { } } - /** - *

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. + * Sets the end of a time interval. + * @param end string containing a date formatted as specified in {@link TimeInterval#DATE_FORMAT_PATTERN} */ public void setEnd(String end) { try { 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 index 58da925cf..5f2387de1 100644 --- a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyVerifier.java +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/usagecontrol/PolicyVerifier.java @@ -2,10 +2,9 @@ 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 de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.LogMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.messages.implementation.NotificationMessageService; +import de.fraunhofer.isst.dataspaceconnector.services.utils.HttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -15,47 +14,59 @@ 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.Map; 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 + * This class provides access permission information for the + * {@link de.fraunhofer.isst.dataspaceconnector.services.usagecontrol.PolicyHandler} depending on the policy content. */ @Component public class PolicyVerifier { - /** Constant LOGGER */ - public static final Logger LOGGER = LoggerFactory.getLogger(PolicyVerifier.class); - private PolicyReader policyReader; - private MessageService messageService; - private HttpUtils httpUtils; + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyVerifier.class); + + private final PolicyReader policyReader; + private final NotificationMessageService notificationMessageService; + private final LogMessageService logMessageService; + private final HttpUtils httpUtils; - @Autowired /** - *

Constructor for PolicyVerifier.

+ * 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. + * @throws IllegalArgumentException if any of the parameters is null. */ - public PolicyVerifier(PolicyReader policyReader, MessageService messageService, HttpUtils httpUtils) { + @Autowired + public PolicyVerifier(PolicyReader policyReader, LogMessageService logMessageService, + NotificationMessageService notificationMessageService, HttpUtils httpUtils) + throws IllegalArgumentException { + if (policyReader == null) + throw new IllegalArgumentException("The PolicyReader cannot be null."); + + if (logMessageService == null) + throw new IllegalArgumentException("The LogMessageService cannot be null."); + + if (notificationMessageService == null) + throw new IllegalArgumentException("The NotificationMessageService cannot be null."); + + if (httpUtils == null) + throw new IllegalArgumentException("The HttpUtils cannot be null."); + this.policyReader = policyReader; - this.messageService = messageService; + this.logMessageService = logMessageService; + this.notificationMessageService = notificationMessageService; this.httpUtils = httpUtils; } /** * Allows data access. * - * @return Access allowed. + * @return true. */ + @SuppressWarnings("SameReturnValue") public boolean allowAccess() { return true; } @@ -63,64 +74,73 @@ public boolean allowAccess() { /** * Inhibits data access. * - * @return Access denied. + * @return false. */ + @SuppressWarnings("SameReturnValue") public boolean inhibitAccess() { return false; } /** - * Saves the access date into the database. + * Saves the access date into the database and allows the access only if that operation was successful. + * TODO: Validate response in more detail. + * TODO: Add log message. * - * @return Success or not (access or inhibition). + * @return true, if the access was logged; false otherwise. */ public boolean logAccess() { + Map response; 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(); + response = logMessageService.sendMessage(""); + } catch (Exception exception) { + LOGGER.warn("Log message could not be sent. [exception=({})]", exception.getMessage()); + return allowAccess(); + } + if (response != null) { + return allowAccess(); + } else { + LOGGER.warn("No response received."); + return allowAccess(); } } /** - * Notify participant about data access. + * Notifies a participant about data access and allows the access only if that operation was successful. + * TODO: Validate response in more detail. * - * @return Success or not (access or inhibition). * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. + * @return true, if the participant was notified; false otherwise. */ public boolean sendNotification(Contract contract) { Rule rule = contract.getPermission().get(0).getPostDuty().get(0); String recipient = policyReader.getEndpoint(rule); + Map response; 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(); + notificationMessageService.setRequestParameters(URI.create(recipient)); + response = notificationMessageService.sendMessage(""); + } catch (Exception exception) { + LOGGER.warn("Notification message could not be sent. [exception=({})]", exception.getMessage()); + return allowAccess(); + } + + if (response != null) { + return allowAccess(); + } else { + LOGGER.warn("No response received."); + return allowAccess(); } } /** * 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. + * @return true, if the current date is within the time interval; false otherwise. */ public boolean checkInterval(Contract contract) { - PolicyReader.TimeInterval timeInterval = policyReader.getTimeInterval(contract.getPermission().get(0)); + PolicyReader.TimeInterval timeInterval = policyReader + .getTimeInterval(contract.getPermission().get(0)); Date date = new Date(); if (date.after(timeInterval.getStart()) && date.before(timeInterval.getEnd())) { @@ -133,20 +153,20 @@ public boolean checkInterval(Contract contract) { /** * 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. + * @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 boolean checkDate(Date dateNow, Date maxAccess) { return dateNow.after(maxAccess); } /** - * Adds a duration to a date to get the a date. + * Adds a duration to a given date and checks if the duration has already been exceeded. * - * @param created The date when the resource was created. - * @return True if the resource should be deleted, false if not. + * @param created the date when the resource was created. * @param contract a {@link de.fraunhofer.iais.eis.Contract} object. + * @return true, the duration has not been exceeded; false otherwise. */ public boolean checkDuration(Date created, Contract contract) { Calendar cal = Calendar.getInstance(); @@ -168,34 +188,37 @@ public boolean checkDuration(Date created, Contract contract) { } /** - * Checks whether the maximum of access number is already reached. + * Checks whether the maximum number of accesses has already been 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. + * @param uuid a {@link java.util.UUID} object. + * @return true, if the maximum number of accesses has not been reached yet; false otherwise. */ 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) { + 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) { + } catch (IOException | RuntimeException e) { return inhibitAccess(); } } /** - * Checks if the duration since resource creation or the max date for resource access has been already exceeded. + * Checks if the specified duration since resource creation or the specified maximum date for resource access + * has already been 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. + * @return true, if the duration or date has been exceeded; false otherwise. + * @throws java.text.ParseException if a duration cannot be parsed. */ public boolean checkForDelete(Rule rule) throws ParseException { Date max = policyReader.getDate(rule); diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/ControllerUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/ControllerUtils.java new file mode 100644 index 000000000..375cf512e --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/ControllerUtils.java @@ -0,0 +1,80 @@ +package de.fraunhofer.isst.dataspaceconnector.services.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.UUID; + +/** + * Contains utility methods for creating ResponseEntities with different status codes and custom messages or exceptions. + */ +public class ControllerUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(ControllerUtils.class); + + /** + * Creates a ResponseEntity with status code 401 and a message indicating an invalid DAT token. + * @param url the URL that was called. + * @return ResponseEntity with status code 401. + */ + public static ResponseEntity respondRejectUnauthorized(String url) { + LOGGER.debug("Unauthorized call. No DAT token found. [url=({})]", url); + return new ResponseEntity<>("Please check your DAT token.", HttpStatus.UNAUTHORIZED); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that an error occurred in the broker + * communication. + * @param exception Exception that was thrown during broker communication. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondBrokerCommunicationFailed(Exception exception) { + LOGGER.debug("Broker communication failed. [exception=({})]", exception.getMessage()); + return new ResponseEntity<>("The communication with the broker failed.", + HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Creates a ResponseEntity with status code 500 and a message indicating that the configuration could not be + * updated. + * @param url the URL that was called. + * @return ResponseEntity with status code 500. + */ + public static ResponseEntity respondUpdateError(String url) { + LOGGER.debug("Configuration error. Could not build current connector. [url=({})]", url); + return new ResponseEntity<>("Configuration error.", HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * 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() { + LOGGER.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(UUID resourceId) { + LOGGER.debug("The resource does not exist. [resourceId=({})]", resourceId); + return new ResponseEntity<>("Resource not found.", 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(UUID resourceId) { + LOGGER.debug("Resource not loaded. [resourceId=({})]", resourceId); + return new ResponseEntity<>("Could not load resource.", + HttpStatus.INTERNAL_SERVER_ERROR); + } +} + // [exception=({})]", exception.getMessage() diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/HttpUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/HttpUtils.java new file mode 100644 index 000000000..9faa12d4c --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/HttpUtils.java @@ -0,0 +1,182 @@ +package de.fraunhofer.isst.dataspaceconnector.services.utils; + +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.util.ClientProvider; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.http.HttpHeaders; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; + +/** + * This class builds up HTTP or HTTPS endpoint connections and sends GET requests. + */ +@Service +public class HttpUtils { + + private final ClientProvider clientProvider; + + /** + * Constructor for HttpUtils. + * + * @throws IllegalArgumentException if any of the parameters is null. + * @throws GeneralSecurityException if the framework has an error. + */ + @Autowired + public HttpUtils(ConfigurationContainer configurationContainer) + throws IllegalArgumentException, GeneralSecurityException { + if (configurationContainer == null) { + throw new IllegalArgumentException("The ConfigurationContainer cannot be null"); + } + + this.clientProvider = new ClientProvider(configurationContainer); + } + + /** + * Sends a GET request to an external HTTP endpoint + * + * @param address the URL. + * @return the HTTP response if HTTP code is OK (200). + * @throws MalformedURLException if the input address is not a valid URL. + * @throws RuntimeException if an error occurred when connecting or processing the HTTP + * request. + */ + public String sendHttpGetRequest(String address) throws MalformedURLException, + RuntimeException { + try { + final var url = new URL(address); + + var con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + final var responseCodeOk = 200; + final var responseCodeUnauthorized = 401; + final var responseMalformed = -1; + + final var responseCode = con.getResponseCode(); + + if (responseCode == responseCodeOk) { + // Request was ok, read the response + try (var in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { + var content = new StringBuilder(); + var inputLine = ""; + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + + return content.toString(); + } + } else if (responseCode == responseCodeUnauthorized) { + // The request is not authorized + throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); + } else if (responseCode == responseMalformed) { + // The response code could not be read + throw new HttpClientErrorException(HttpStatus.EXPECTATION_FAILED); + } else { + // This function should never be thrown + throw new NotImplementedException("Unsupported return value " + + "from getResponseCode."); + } + + } catch (MalformedURLException exception) { + // The parameter address is not an url. + throw exception; + } catch (Exception exception) { + // Catch all the HTTP, IOExceptions + throw new RuntimeException("Failed to send the http get request.", exception); + } + } + + /** + * Sends a GET request to an external HTTPS endpoint + * + * @param address the URL. + * @return the HTTP body of the response when HTTP code is OK (200). + * @throws MalformedURLException if the input address is not a valid URL. + * @throws RuntimeException if an error occurred when connecting or processing the HTTP + * request. + */ + public String sendHttpsGetRequest(String address) + throws MalformedURLException, RuntimeException { + try { + final var request = new Request.Builder().url(address).get().build(); + + var client = clientProvider.getClient(); + Response response = client.newCall(request).execute(); + + if (response.code() < 200 || response.code() >= 300) { + response.close(); + // Not the expected response code + throw new HttpClientErrorException(HttpStatus.EXPECTATION_FAILED); + } else { + // Read the response + final var rawResponseString = + new String(response.body().byteStream().readAllBytes()); + response.close(); + + return rawResponseString; + } + } catch (MalformedURLException exception) { + // The parameter address is not an url. + throw exception; + } catch (Exception exception) { + // Catch all the HTTP, IOExceptions + throw new RuntimeException("Failed to send the http get request.", exception); + } + } + + /** + * 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 when HTTP code is OK (200). + * @throws MalformedURLException if the input address is not a valid URL. + * @throws RuntimeException if an error occurred when connecting or processing the HTTP + * request. + */ + public String sendHttpsGetRequestWithBasicAuth(String address, String username, + String password) throws MalformedURLException, RuntimeException { + final var auth = username + ":" + password; + final var encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); + final var authHeader = "Basic " + new String(encodedAuth); + + try { + final var request = new Request.Builder().url(address) + .header(HttpHeaders.AUTHORIZATION, authHeader).get().build(); + + final var client = clientProvider.getClient(); + final var response = client.newCall(request).execute(); + + if (response.code() < 200 || response.code() >= 300) { + response.close(); + // Not the expected response code + throw new HttpClientErrorException(HttpStatus.EXPECTATION_FAILED); + } else { + String rawResponseString = new String(response.body().byteStream().readAllBytes()); + response.close(); + + return rawResponseString; + } + } catch (MalformedURLException exception) { + // The parameter address is not an url. + throw exception; + } catch (Exception exception) { + // Catch all the HTTP, IOExceptions + throw new RuntimeException("Failed to send the http get request.", exception); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/IdsUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/IdsUtils.java new file mode 100644 index 000000000..b431c40d4 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/IdsUtils.java @@ -0,0 +1,228 @@ +package de.fraunhofer.isst.dataspaceconnector.services.utils; + +import de.fraunhofer.iais.eis.*; +import de.fraunhofer.iais.eis.util.ConstraintViolationException; +import de.fraunhofer.iais.eis.util.TypedLiteral; +import de.fraunhofer.iais.eis.util.Util; +import de.fraunhofer.isst.dataspaceconnector.exceptions.ConnectorConfigurationException; +import de.fraunhofer.isst.dataspaceconnector.model.ConnectorResource; +import de.fraunhofer.isst.ids.framework.configuration.ConfigurationContainer; +import de.fraunhofer.isst.ids.framework.configuration.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.util.ArrayList; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * This class provides methods to map local connector models to IDS Information Model objects. + */ +@Service +public class IdsUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(IdsUtils.class); + + private final ConfigurationContainer configurationContainer; + private final SerializerProvider serializerProvider; + + /** + * Constructor for IdsUtils. + * + * @throws IllegalArgumentException if any of the parameters is null. + */ + @Autowired + public IdsUtils(ConfigurationContainer configurationContainer, + SerializerProvider serializerProvider) throws IllegalArgumentException { + if (configurationContainer == null) + throw new IllegalArgumentException("The ConfigurationContainer cannot be null."); + + if (serializerProvider == null) + throw new IllegalArgumentException("The SerializerProvider cannot be null."); + + this.configurationContainer = configurationContainer; + this.serializerProvider = serializerProvider; + } + + /** + * Returns the current IDS base connector object from the application context. + * + * @return The {@link de.fraunhofer.iais.eis.Connector} object from the IDS Framework. + * @throws ConnectorConfigurationException If the connector was not found. + */ + public Connector getConnector() throws ConnectorConfigurationException { + final var connector = configurationContainer.getConnector(); + if (connector == null) { + // The connector is needed for every answer and cannot be null + throw new ConnectorConfigurationException("No connector configured."); + } + + return connector; + } + + /** + * Maps a resource metadata object to the corresponding Information Model object. + * + * @param resource the connector resource. + * @return the Information Model resource. + * @throws RuntimeException if the Information Model object could not be build. + */ + public Resource getAsResource(ConnectorResource resource) throws RuntimeException { + final var language = getDefaultLanguage(); + final var metadata = resource.getResourceMetadata(); + if (metadata == null) { + throw new NullPointerException("The metadata cannot be null."); + } + + // Get the list of keywords + var keywords = new ArrayList(); + if (metadata.getKeywords() != null) { + for (var keyword : metadata.getKeywords()) { + keywords.add(new TypedLiteral(keyword, language)); + } + } + + // Get the list of representations + var representations = new ArrayList(); + if (metadata.getRepresentations() != null) { + for (var representation : metadata.getRepresentations().values()) { + try { + 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())) + ._fileName_(representation.getName()) + .build())) + .build()); + } catch (ConstraintViolationException exception) { + throw new RuntimeException("Failed to build resource representation.", + exception); + } + } + } + + // Get the list of contracts. + var contracts = new ArrayList(); + if (metadata.getPolicy() != null) { + try { + // Add the provider to the contract offer. + final var contractOffer = serializerProvider.getSerializer() + .deserialize(metadata.getPolicy(), ContractOffer.class); + contracts.add(new ContractOfferBuilder() + ._permission_(contractOffer.getPermission()) + ._prohibition_(contractOffer.getProhibition()) + ._obligation_(contractOffer.getObligation()) + ._contractStart_(contractOffer.getContractStart()) + ._contractDate_(contractOffer.getContractDate()) + ._consumer_(contractOffer.getConsumer()) + ._provider_(getConnector().getId()) + ._contractEnd_(contractOffer.getContractEnd()) + ._contractAnnex_(contractOffer.getContractAnnex()) + ._contractDocument_(contractOffer.getContractDocument()) + .build()); + } catch (IOException exception) { + LOGGER.debug(String.format("Could not deserialize contract.\nContract: [%s]", + metadata.getPolicy()), exception); + throw new RuntimeException("Could not deserialize contract.", exception); + } + } + + // Build the ids resource. + try { + return new ResourceBuilder( + URI.create("https://w3id.org/idsa/autogen/resource/" + resource.getUuid())) + ._contractOffer_(contracts) + ._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(); + } catch (ConstraintViolationException | NullPointerException exception) { + // The build failed or the connector is null. + throw new RuntimeException("Failed to build information model resource.", exception); + } + } + + /** + * Converts a date to XMLGregorianCalendar format. + * + * @param date the date object. + * @return the XMLGregorianCalendar object or null. + */ + public XMLGregorianCalendar getGregorianOf(Date date) { + GregorianCalendar c = new GregorianCalendar(); + c.setTime(date); + try { + return DatatypeFactory.newInstance().newXMLGregorianCalendar(c); + } catch (DatatypeConfigurationException exception) { + // Rethrow but do not register in function header + throw new RuntimeException(exception); + } + } + + /** + * Gets the default language, which is the first set language of the connector. + * + * @return the default language of the connector. + * @throws ConnectorConfigurationException if the connector is null or no language is + * configured. + */ + private String getDefaultLanguage() throws ConnectorConfigurationException { + try { + return getLanguage(0); + } catch (IndexOutOfBoundsException exception) { + throw new ConnectorConfigurationException("No default language has been set."); + } + } + + /*** + * Gets a language set for the connector from the application context. + * + * @param index index of the language. + * @return the language at the passed index. + * @throws ConnectorConfigurationException if the connector is null or no language is set. + * @throws IndexOutOfBoundsException if no language could be found at the passed index. + */ + @SuppressWarnings("SameParameterValue") + private String getLanguage(int index) + throws ConnectorConfigurationException, IndexOutOfBoundsException { + try { + final var label = configurationContainer.getConnector().getLabel(); + if (label.size() == 0) { + throw new ConnectorConfigurationException("No language has been set."); + } + + final var language = label.get(index).getLanguage(); + + if (language.isEmpty()) { + throw new ConnectorConfigurationException("No language has been set."); + } + + return language; + } catch (NullPointerException exception) { + throw new ConnectorConfigurationException("The connector language configuration could" + + " not be received.", exception); + } + } +} diff --git a/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/UUIDUtils.java b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/UUIDUtils.java new file mode 100644 index 000000000..e4f9e8862 --- /dev/null +++ b/src/main/java/de/fraunhofer/isst/dataspaceconnector/services/utils/UUIDUtils.java @@ -0,0 +1,127 @@ +package de.fraunhofer.isst.dataspaceconnector.services.utils; + +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDCreationException; +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import org.jetbrains.annotations.NotNull; + +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 class UUIDUtils { + + /** + * Finds all UUIDs in a string. + * + * @param input a string which maybe contains UUIDs. + * @return the list of found UUIDs. + */ + public static List findUuids(@NotNull 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(@NotNull 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(@NotNull URI uri, 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 + */ + public static UUID createUUID(Function doesUuidExistFunc) + throws UUIDCreationException { + return createUUID(doesUuidExistFunc, 32); + } + + /** + * 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(Function doesUuidExistFunc, 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/resources/application.properties b/src/main/resources/application.properties index c742a1bab..102462919 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,12 +15,43 @@ springdoc.swagger-ui.operationsSorter=method ## TLS server.ssl.enabled=true -server.ssl.key-store-policyType=PKCS12 +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 +## General Information +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@ + +## 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 + +######################################################################################################################## +## 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 ## ######################################################################################################################## @@ -33,7 +64,7 @@ spring.datasource.password=password ## Enable H2 Console Access spring.h2.console.enabled=true -spring.h2.console.path=/admin/h2 +spring.h2.console.path=/admin/database spring.h2.console.settings.web-allow-others=true ## Import Data @@ -44,25 +75,8 @@ spring.h2.console.settings.web-allow-others=true spring.jpa.hibernate.ddl-auto=update # spring.jpa.hibernate.ddl-auto=create -# Hibernate Logging -logging.level.org.hibernate.SQL= DEBUG - ## MULTIPART (MultipartProperties) spring.servlet.multipart.enabled=true spring.servlet.multipart.file-size-threshold=2KB spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-request-size=215MB - -######################################################################################################################## -## 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/.well-known/jwks.json diff --git a/src/main/resources/conf/config.json b/src/main/resources/conf/config.json index 629b108e3..d31f8e699 100644 --- a/src/main/resources/conf/config.json +++ b/src/main/resources/conf/config.json @@ -26,7 +26,7 @@ "@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", @@ -73,6 +73,10 @@ "@id" : "https://localhost:8080/" }, { "@id" : "http://localhost:8080/" + }, { + "@id" : "https://localhost:8081/" + }, { + "@id" : "http://localhost:8081/" } ] } ] } 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..b335dda8c --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,35 @@ + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5p %m%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/de/fraunhofer/isst/dataspaceconnector/UUIDUtilsTests.java b/src/test/java/de/fraunhofer/isst/dataspaceconnector/UUIDUtilsTests.java new file mode 100644 index 000000000..30dcaae9d --- /dev/null +++ b/src/test/java/de/fraunhofer/isst/dataspaceconnector/UUIDUtilsTests.java @@ -0,0 +1,66 @@ +package de.fraunhofer.isst.dataspaceconnector; + +import de.fraunhofer.isst.dataspaceconnector.exceptions.UUIDFormatException; +import de.fraunhofer.isst.dataspaceconnector.services.utils.UUIDUtils; +import java.net.URI; +import java.util.UUID; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class UUIDUtilsTests { + + @Test(expected = UUIDFormatException.class) + public void UUIDUtils_uuidFromUri_Uri_without_Uuid_In() { + final var uuidString = ""; + final var uriString = "https://dsc/anon42/api/" + uuidString; + + final var inputUri = URI.create(uriString); + + UUIDUtils.uuidFromUri(inputUri); + } + + @Test + public void UUIDUtils_uuidFromUri_Uri_with_one_Uuid_InOut() { + 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); + + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + Assert.assertEquals(resultUUID, expectedUUID); + } + + @Test + public void UUIDUtils_uuidFromUri_Uri_with_two_seperated_Uuids_In_LastOut() { + 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); + + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + Assert.assertEquals(resultUUID, expectedUUID); + } + + + @Test + public void UUIDUtils_uuidFromUri_Uri_with_two_combined_Uuids_In_LastOut() { + 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); + + final var resultUUID = UUIDUtils.uuidFromUri(inputUri); + + Assert.assertEquals(resultUUID, expectedUUID); + } + +} 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()); - } - -}