Merging dev to main #107

Merged
Maece97 merged 52 commits from dev into main 2021-12-23 16:12:19 +00:00
248 changed files with 4424 additions and 2391 deletions

View File

@ -67,6 +67,9 @@ services:
- ./:/data/ - ./:/data/
environment: environment:
mqtt.broker.uri: tcp://broker.hivemq.com:1883 mqtt.broker.uri: tcp://broker.hivemq.com:1883
discovery.endpoint.uri: https://tapas-auction-house.86-119-34-242.nip.io
auction.house.uri: https://tapas-auction-house.86-119-35-40.nip.io
tasks.list.uri: https://tapas-tasks.86-119-35-40.nip.io
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.tapas-auction-house.rule=Host(`tapas-auction-house.${PUB_IP}.nip.io`)" - "traefik.http.routers.tapas-auction-house.rule=Host(`tapas-auction-house.${PUB_IP}.nip.io`)"
@ -86,8 +89,8 @@ services:
- ./:/data/ - ./:/data/
environment: environment:
task.list.uri: http://tapas-tasks:8081 task.list.uri: http://tapas-tasks:8081
executor.robot.uri: http://executor-robot:8084 auction.house.uri: http://tapas-auction-house:8086
executor.computation.uri: http://executor-computation:8085 executor.pool.uri: http://executor-pool:8083
mqtt.broker.uri: tcp://broker.hivemq.com:1883 mqtt.broker.uri: tcp://broker.hivemq.com:1883
spring.data.mongodb.uri: mongodb://root:password@tapas-db:27017 spring.data.mongodb.uri: mongodb://root:password@tapas-db:27017
labels: labels:
@ -130,13 +133,15 @@ services:
volumes: volumes:
- ./:/data/ - ./:/data/
environment: environment:
EXECUTOR_POOL_URI: http://executor-pool:8083 executor.type: COMPUTATION
ROSTER_URI: http://roster:8082 executor.uri: http://executor-computation:8085
executor.pool.uri: http://executor-pool:8083
roster.uri: http://roster:8082
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)" - "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)"
- "traefik.http.routers.executor-computation.service=executor-computation" - "traefik.http.routers.executor-computation.service=executor-computation"
- "traefik.http.services.executor-computation.loadbalancer.server.port=8084" - "traefik.http.services.executor-computation.loadbalancer.server.port=8085"
- "traefik.http.routers.executor-computation.tls=true" - "traefik.http.routers.executor-computation.tls=true"
- "traefik.http.routers.executor-computation.entryPoints=web,websecure" - "traefik.http.routers.executor-computation.entryPoints=web,websecure"
- "traefik.http.routers.executor-computation.tls.certresolver=le" - "traefik.http.routers.executor-computation.tls.certresolver=le"
@ -151,13 +156,38 @@ services:
volumes: volumes:
- ./:/data/ - ./:/data/
environment: environment:
EXECUTOR_POOL_URI: http://executor-pool:8083 executor.type: SMALLROBOT
ROSTER_URI: http://roster:8082 executor.uri: http://executor-robot:8084
executor.pool.uri: http://executor-pool:8083
roster.uri: http://roster:8082
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)" - "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)"
- "traefik.http.routers.executor-robot.service=executor-robot" - "traefik.http.routers.executor-robot.service=executor-robot"
- "traefik.http.services.executor-robot.loadbalancer.server.port=8085" - "traefik.http.services.executor-robot.loadbalancer.server.port=8084"
- "traefik.http.routers.executor-robot.tls=true" - "traefik.http.routers.executor-robot.tls=true"
- "traefik.http.routers.executor-robot.entryPoints=web,websecure" - "traefik.http.routers.executor-robot.entryPoints=web,websecure"
- "traefik.http.routers.executor-robot.tls.certresolver=le" - "traefik.http.routers.executor-robot.tls.certresolver=le"
executor-humidity:
image: openjdk
command: "java -jar /data/executor-humidity-0.0.1-SNAPSHOT.jar"
restart: unless-stopped
depends_on:
- executor-pool
- roster
- tapas-db
volumes:
- ./:/data/
environment:
executor.type: HUMIDITY
executor.uri: http://executor-humidity:8087
executor.pool.uri: http://executor-pool:8083
roster.uri: http://roster:8082
labels:
- "traefik.enable=true"
- "traefik.http.routers.executor-computation.rule=Host(`executor-humidity.${PUB_IP}.nip.io`)"
- "traefik.http.routers.executor-computation.service=executor-computation"
- "traefik.http.services.executor-computation.loadbalancer.server.port=8087"
- "traefik.http.routers.executor-computation.tls=true"
- "traefik.http.routers.executor-computation.entryPoints=web,websecure"
- "traefik.http.routers.executor-computation.tls.certresolver=le"

View File

@ -55,6 +55,10 @@ jobs:
run: mvn -f executor-robot/pom.xml --batch-mode --update-snapshots verify run: mvn -f executor-robot/pom.xml --batch-mode --update-snapshots verify
- run: cp ./executor-robot/target/executor-robot-0.0.1-SNAPSHOT.jar ./target - run: cp ./executor-robot/target/executor-robot-0.0.1-SNAPSHOT.jar ./target
- name: Build executor-humidity service
run: mvn -f executor-humidity/pom.xml --batch-mode --update-snapshots verify
- run: cp ./executor-humidity/target/executor-humidity-0.0.1-SNAPSHOT.jar ./target
- name: Build tapas-task service - name: Build tapas-task service
run: mvn -f tapas-tasks/pom.xml --batch-mode --update-snapshots verify run: mvn -f tapas-tasks/pom.xml --batch-mode --update-snapshots verify
- run: cp ./tapas-tasks/target/tapas-tasks-0.0.1-SNAPSHOT.jar ./target - run: cp ./tapas-tasks/target/tapas-tasks-0.0.1-SNAPSHOT.jar ./target
@ -103,5 +107,6 @@ jobs:
cd /home/${{ secrets.SSH_USER }}/ cd /home/${{ secrets.SSH_USER }}/
touch acme.json touch acme.json
sudo chmod 0600 acme.json sudo chmod 0600 acme.json
sudo docker-compose down --remove-orphans
sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env
sudo docker-compose up -d --build --force-recreate sudo docker-compose up -d

149
README.md
View File

@ -1,116 +1,54 @@
# TAPAS # TAPAS
This is the main GitHub project for your implementation of the TAPAS application. This is the main GitHub project for the implementation of the TAPAS application from Group 1.
## Run application in developent
We use Docker & docker-compose in development to easly start all the microservices and other needed application (db's, message-broker's) at once. All microservices have hot-reloads enabled by default!
#### Start
```
docker-compose up
```
#### Rebuild container
```
docker-compose up --build
```
#### Start detached
```
docker-compose up -d
```
#### Stop detached
```
docker-compose down
```
## Available Services
Ports and debug ports of each service are listed below:
| Name | Port | Debug Port |
| ------------------ | ---- | ---------- |
| Tasklist | 8081 | 5005 |
| Assignment Service | 8082 | 5006 |
| Executor Pool | 8083 | 5007 |
| Executor 1 | 8084 | 5008 |
| Executor 2 | 8085 | 5009 |
## Project Structure ## Project Structure
This project is structured as follows: This project is structured as follows:
- [.deployment/docker-compose.yml](.deployment/docker-compose.yml): Docker Compose configuration file for all services (deployment)
- [.experiments](.experiments): Experiment files for chaos monkey tests
- [.github/workflows](.github/workflows): GitHub actions scripts (CI/CD workflow)
- [common](common): common library for shared elements across the whole application
- [common/src](common/src): source code of the library
- [common/pom.xml](common/pom.xml): Maven pom-file
- [doc/architecture/decisions](doc/architecture/decisions): ADRs
- [doc/workflow.png](doc/workflow.png): Workflow diagram
- [executor-base](executor-base): library for the executors. Includes the logic for executors to connect to the system
- [executor-base/src](executor-base/src): source code of the library
- [executor-base/pom.xml](executor-base/pom.xml): Maven pom-file
- [executor-computation](executor-computation): standalone project for the computation executor micro-service (Spring Boot project)
- [executor-computation/src](executor-computation/src): source code of the project
- [executor-computation/pom.xml](executor-computation/pom.xml): Maven pom-file
- [executor-humidity](executor-humidity): standalone project for the humidity executor micro-service (Spring Boot project)
- [executor-humidity/src](executor-humidity/src): source code of the project
- [executor-humidity/pom.xml](executor-humidity/pom.xml): Maven pom-file
- [executor-pool](executor-pool): standalone project for the executor-pool micro-service (Spring Boot project)
- [executor-pool/src](executor-pool/src): source code of the project (following the Hexagonal Architecture)
- [executor-pool/pom.xml](executor-pool/pom.xml): Maven pom-file
- [executor-robot](executor-robot): standalone project for the robot executor micro-service (Spring Boot project)
- [executor-robot/src](executor-robot/src): source code of the project
- [executor-robot/pom.xml](executor-robot/pom.xml): Maven pom-file
- [mocks](mocks): some auction-house mock files to test localy
- [roster](roster): standalone project for the Roster micro-service (Spring Boot project)
- [roster/src](roster/src): source code of the project (following the Hexagonal Architecture)
- [roster/pom.xml](roster/pom.xml): Maven pom-file
- [tapas-auction-house](tapas-auction-house): standalone project for the Tapas-Aution-House micro-service (Spring Boot project)
- [tapas-auction-house/src](tapas-auction-house/src): source code of the project (following the Hexagonal Architecture)
- [tapas-auction-house/pom.xml](tapas-auction-house/pom.xml): Maven pom-file
- [tapas-tasks](tapas-tasks): standalone project for the Tapas-Tasks micro-service (Spring Boot project) - [tapas-tasks](tapas-tasks): standalone project for the Tapas-Tasks micro-service (Spring Boot project)
- [tapas-tasks/src](tapas-tasks/src): source code of the project (following the Hexagonal Architecture) - [tapas-tasks/src](tapas-tasks/src): source code of the project (following the Hexagonal Architecture)
- [tapas-tasks/pom.xml](tapas-tasks\pom.xml): Maven pom-file - [tapas-tasks/pom.xml](tapas-tasks/pom.xml): Maven pom-file
- [app](app): folder as placeholder for a second micro-service (Spring Boot project)
- [docker-compose.yml](docker-compose.yml): Docker Compose configuration file for all services
- [.github/workflows/build-and-deploy.yml](.github/workflows/build-and-deploy.yml): GitHub actions script (CI/CD workflow)
## How to Add a New Service with Spring Boot - [docker-compose.yml](docker-compose.yml): Docker Compose configuration file for local development
### Create a new Spring Boot project
- Recommended: use [Spring Initialzr](https://start.spring.io/) (Maven, Spring Boot 2.5.5, Jar, Java 11, dependencies as needed)
- Set the Spring application properties for your service (e.g., port of the web server) in `src/resources/application.properties`
### Update the Docker Compose file
Your TAPAS application is a multi-container Docker application ran with [Docker Compose](https://docs.docker.com/compose/).
To add your newly created service to the Docker Compose configuration file, you need to create a new service
definition in [docker-compose.yml](docker-compose.yml):
- copy and edit the `tapas-tasks` service definition from [lines 29-42](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/docker-compose.yml#L29-L42)
- change `command` (see [line 31](https://github.com/scs-asse/tapas/blob/main/docker-compose.yml#L31))
to use the name of the JAR file generated by Maven for your service
- note: if you change the version of your service, you need to update this line to reflect the change
- update the Traefik label names to reflect the name of your new service (see [lines 37-42](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/docker-compose.yml#L37-L42))
- e.g., change `traefik.http.routers.tapas-tasks.rule` to `traefik.http.routers.<new-service-name>.rule`
- update the Traefik `rule` (see [line 37](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/docker-compose.yml#L37)) with the name of your new service: `` Host(`<new-service-name>.${PUB_IP}.nip.io`) ``
- update the Traefik `port` (see [line 39](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/docker-compose.yml#L39)) with the port configured for your new service
### Update the GitHub Actions Workflow
This project uses GitHub Actions to build and deploy your TAPAS application whenever a new commit is
pushed on the `main` branch. You can add your new service to the GitHub Actions workflow defined in
[.github/workflows/build-and-deploy.yml](.github/workflows/build-and-deploy.yml):
- copy and edit the definition for `tapas-tasks` from [line 28-30](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/.github/workflows/build-and-deploy.yml#L28-L30)
- update the `mvn` command used to build your service to point to the `pom.xml` file of your new service (see [line 29](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/.github/workflows/build-and-deploy.yml#L29))
- update the `cp` command to point to the JAR file of your new service directive (see [line 30](https://github.com/scs-asse/tapas/blob/424a5f5aa2d6524acfe95d93000571884ed9d66f/.github/workflows/build-and-deploy.yml#L30))
- note you will need to update the complete file path (folder structure and JAR name)
### How to Run Your Service Locally
You can run and test your micro-service on your local machine just like a regular Maven project:
- Run from IntelliJ:
- Reload _pom.xml_ if necessary
- Run the micro-service's main class from IntelliJ for all required projects
- Use Maven to run from the command line:
```shell
mvn spring-boot:run
```
## How to Deploy on your VM
1. Start your Ubuntu VM on Switch.
- VM shuts down automatically at 2 AM
- Group admins can do this via https://engines.switch.ch/horizon
2. Push new code to the _main_ branch
- Check the status of the workflow on the _Actions_ page of the GitHub project
- We recommend to test your project locally before pushing the code to GitHub. The GitHub Organizations
used in the course are on a free tier plan, which comes with [various limits](https://github.com/pricing).
3. Open in your browser `https://app.<server-ip>.nip.io`
For the server IP address (see below), you should use dashes instead of dots, e.g.: `127.0.0.1` becomes `127-0-0-1`.
## VM Configurations ## VM Configurations
@ -128,8 +66,3 @@ Specs (we can upgrade if needed):
| SCS-ASSE-VM-Group3 | 86.119.34.242 | | SCS-ASSE-VM-Group3 | 86.119.34.242 |
| SCS-ASSE-VM-Group4 | 86.119.35.199 | | SCS-ASSE-VM-Group4 | 86.119.35.199 |
| SCS-ASSE-VM-Group5 | 86.119.35.72 | | SCS-ASSE-VM-Group5 | 86.119.35.72 |
## Architecture Decision Records
We recommend you to use [adr-tools](https://github.com/npryce/adr-tools) to manage your ADRs here in
this GitHub project in a dedicated folder. The tool works best on a Mac OS or Linux machine.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

View File

@ -0,0 +1,25 @@
# 1. microservice architecture
Date: 2021-12-02
## Status
Accepted
## Context
The system is made up of five distinct bounded contexts, namely the Task Domain, the Roster Domain, the Executor Pool Domain, the Executor Domain, and the Auction Domain. The way that these bounded contexts function together to fulfil the system requirements can be based on many different architectures.
## Decision
The system will follow the Microservice architecture.
The Microservices architecture suits our system's driving architectural characteristics particularly well. Scalability and fault tolerance are two of the systems top 3 driving characteristics and elasticity and evolvability are two of the systems other driving characteristics. These are all characteristics that the Microservices architecture excels at. Furthermore, none of our system's driving characteristics is hindered by the Microservices architecture (none of them have below 3 stars out of 5).
We do not expect to have a single monolithic database, so this is not a concern.
## Consequences
There is a considerable amount of communication between bounded contexts. This could cause responsiveness and performance issues due to added latency. This could therefore mean we would need to use asynchronous REST calls or publish-subscribe communication to mitigate these issues as much of the communication does not have to be synchronous.
Using a distributed architecture makes managing transactions more complex. This means that we could need to use sagas to manage distributed transactions. So far the only workflow that would require transactions between domains would be the deletion of tasks.

View File

@ -0,0 +1,24 @@
# 5. Event driven communication
Date: 2021-10-18
## Status
Accepted
## Context
The TAPAS system will be implemented following the Microservices architecture. Each service encapsulates a different bounded context with different functional and non-functional requirements. The TAPAS system could either be implemented as Synchronous Microservices or Event-Driven Microservices.
## Decision
The TAPAS system could either be implemented as Event-Driven Microservices.
Event-driven asynchronous communication suits the TAPAS system better than synchronous communication. This will facilitate looser coupling between services.
Scalability, one of the system's top 3 driving architectural characteristics, will be positively impacted as individual services can be scaled up and down as needed.
Responsiveness (another of the system's driving architectural characteristics) will also improve as services do not need to wait for other services to respond before responding themselves.
## Consequences
Asynchronous communication makes error handling more complex. At this moment in time we do not see this coming a problem. However, if the error handling becomes overly complex, then we might need to implement the workflow event pattern in order to combat this increased complexity.

View File

@ -0,0 +1,26 @@
# 3. Separate service for the Roster
Date: 2021-11-21
## Status
Accepted
## Context
The Task domain includes the creation and deletion of tasks from the system. It also stores all tasks and keeps track of a task's status. The Roster domain keeps track of internal task execution. It receives tasks from the Task domain. If the task can be executed internally then it queues it and keeps track of it until it has been executed by an internal executor. If it cannot be executed internally then it sends the task on to the Auction domain. These two domains could be implemented as a consolidated service or as two separate services.
## Decision
We will create two separate services; a Task List service and a Roster service.
Firstly, having two separate services will improve the system's fault tolerance. If the Task List goes down then the Roster can keep on managing the internal execution of tasks. Similarly, if the Roster goes down then the Task List can keep on being receiving input from users. Since we use asynchronous messaging for internal communication, the services can process new events when they come back up. The creation and execution of tasks is the heartbeat of the TAPAS system. Therefore, making it more fault tolerant is critical.
Secondly, we don't expect the Task List to change as frequently as the Roster. The Task List usually just needs to change when there are large new features that will impact more than just the Task domain. These large changes will be not very frequent. Conversely, we expect the Roster to change more frequently due to changes in the assignment algorithm, communication with executors, or error handling.
## Consequences
Before deleting a task from the Task List we first need to delete it from the Roster (and make sure it is not being executed). Having these as two separate services makes this transaction more complicated to manage.
Moreover, the services have to talk a lot to each other and having them seperated will add latency between them.

View File

@ -1,4 +1,4 @@
# 12. seperate service for each executor # 4. Separate service for each Executor
Date: 2021-11-21 Date: 2021-11-21
@ -12,13 +12,13 @@ Executors must receive tasks of different types and execute them. The executors
## Decision ## Decision
We will have a seperate service for each type of executor. We will have a separate service for each type of executor.
Firstly, execution time differs significantly between task types. Therefore, having seperate services will allow the executos to scale differently based on their tasks' specific needs. Firstly, execution time differs significantly between task types. Therefore, having seperate services will allow the executors to scale differently based on their tasks' specific needs.
Secondly, the systems functioning should not be disrupted in case an Executor fails. Having each type of executor in a seperate service will increase fault tolerance in this regard. Secondly, the systems functioning should not be disrupted in case an Executor fails. Having each type of executor in a separate service will increase fault tolerance in this regard.
Lastly, extensibilty is one of the systems most important non-functional requirement and providers of executors need to be able to easily add executors to the executor pool. These factors are also positively impacted by having seperate services. Lastly, evolvability is one of the systems most important non-functional requirement and providers of executors need to be able to easily add executors to the executor pool. These factors are also positively impacted by having seperate services.
There should not be any shared data between the executors. Additionally, there should be no flow of information between them. Thus, there should be no issues due to workflow and data concerns due to this decision. There should not be any shared data between the executors. Additionally, there should be no flow of information between them. Thus, there should be no issues due to workflow and data concerns due to this decision.

View File

@ -0,0 +1,26 @@
# 5. Separate service for the Executor Pool
Date: 2021-11-21
## Status
Accepted
## Context
The Executor Pool has to keep track of which Executors are online and what task types they can execute. The Roster keeps track of tasks that need to be executed and assigns Executors to tasks. The Executor Pool functionality could be implemented in a separate service or as a part of the Roster.
## Decision
The executor pool will be implemented as a separate service.
Most importantly, the Executor Pool and the Roster need to scale differently. The Roster needs to scale depending on the number of tasks in the system while the Executor Pool needs to scale based on the number of Executors in the system. As the number of tasks is likely to far exceed the number of Executors, the two services should scale differently.
Another reason why the two should be separate services is that they have quite different responsibilities and reasons to change. On one hand, the Executor Pool manages the Executors, and therefore it will change if we want to add functionality that impacts how Executors log onto the system and how we keep track of them. For example, if we want to check if an executor fulfills some requirements before logging on. On the other hand, the Roster manages the internal execution of tasks.
Lastly, separating the two will improve the fault tolerance of the system. If the Executor Pool goes down then the Roster will keep being able to delegate tasks to internal executors, although the Roster will not be notified about added/removed executors while the Executor Pool is down. This should however not be a problem if the Executor Pool is only down for a short amount of time. Similarly, the Executor Pool can continue to keep track of added and removed executors while the Roster is down.
## Consequences
Having the services separate will add latency between an executor being added to the Executor Pool and the Roster being notified about it. However, it is only really critical that the Roster is notified about new Executors that execute a new Task Type, which should happen relatively rarely. Therefore, a small increase in latency to this workflow should not have a large effect on the overall operation of the system.

View File

@ -0,0 +1,29 @@
# 7. Separate service for Auction House
Date: 2021-11-21
## Status
Accepted
## Context
The Auction House has to launch auctions and manage auctions for tasks that we cannot execute internally. Moreover, it has to subscribe to other Auction Houses and bid on their auctions. The Roster keeps track of tasks that need to be executed and assigns Executors to tasks. Additionally, the Roster passes tasks that cannot be executed internally onto the Auction House. The Auction House functionality can either be implemented as a separate service or as a part of the Roster.
## Decision
The Auction House will be implemented as a separate service.
Most importantly, the Auction House and the Roster need to scale differently. The Roster needs to scale depending on the number of tasks in the system as all tasks go through the Roster initially. We predict that the majority of tasks will be executed internally. The Auction House also needs to scale depending on the number of external auctions. Moreover, the Auction House has different throughput as the Auction House has to communicate synchronously with a number of external clients.
Furthermore, separating the Auction House from the Roster will allow us to isolate the Roster from external communication. This improves the security of the TAPAS system as the Roster is more critical to the functioning of the system than the Auction House. Opening the Roster up to external communication could for example expose it to denial of service attacks.
Similarly, having two services separate will improve fault tolerance as either can fail without taking down the other. This is more important for the Roster as it can keep the main workflow of executing tasks going if the Auction House goes down.
## Consequences
Latency is added to the communication between the two domains. However, this should not have a major impact on the overall functioning of the system.
Data needs to be duplicated since both services need to keep track of the executors available in our system.
Deleting of tasks becomes an even more complex transaction as now we also need to check the Auction House to see if the task is being auctioned before deleting (we don't want to delete a task that another group will then go on to execute). This transaction now involves three services (the Task List, the Roster, and the Auction House). We will most likely need to implement a saga pattern to manage this transaction properly.

View File

@ -0,0 +1,23 @@
# 7. Common library for shared code
Date: 2021-11-21
## Status
Accepted
## Context
The numerous services that make up the Tapas app all have common, non-domain specific, functionality that can be shared via a common library, or be replicated in each service.
## Decision
We will use a common code library for shared code which does not change frequently, but if it would be changed, would need to be changed everywhere.
This improves maintainability as you only have to change the code in one place. This does not only save time, but also reduces the likelihood of forgetting to replace it in one service which could introduce bugs.
## Consequences
Changes in the common code will most likely require multiple services to be redeployed. However, those services would most likely have to have been changed individually and redeployed anyways.
Another consequence is that versioning becomes more complicated.

View File

@ -0,0 +1,29 @@
# 8. Executor base library
Date: 2021-11-21
## Status
Accepted
## Context
According to the project requirements, Executors can be developed by other teams within the organisation. Executors all use the same logic to communicate with other services. This means that their code base is near identical except for the code that implements their specific task execution. The code that implements the executor's shared logic can either be implemented by a shared library, or replicated across all executors.
## Decision
We will implement a shared library for common Executor functionality.
All executors use the same logic to communicate with other services, any change to this logic would have to be made for every executor. Having this shared logic in a separate library makes it easy to change the common logic at one place. The code sharing happens at compile time, which reduces the chance of runtime errors, compared to other code sharing approaches.
Additionally, if other teams need to create executors, they can just reference the executor-base library and implement the actual execution part. Therefore, they dont need to worry about the connection implementations to the TAPAS system.
## Consequences
Using a shared library will increase the complexity of the executors.
Changes in the common code will most likely require multiple services to be redeployed. However, those services would most likely have to have been changed individually and redeployed anyways.
Another consequence is that versioning becomes more complicated.
Lastly, we have to make sure that we don't become over reliant on everyone using this library to communicate with the TAPAS system. Future IoT Executors might want to use a more lightweight way to communicate, so we will have to be aware of this.

View File

@ -0,0 +1,21 @@
# 9. Separation of common and executor-base library
Date: 2021-11-21
## Status
Accepted
## Context
We have two code sharing libraries, the executor-base and the common library. The executor-base implements shared logic that all executors need, but other services don't. The common library has much more wide-reaching implementations, such as the implementation of the SelfValidating class. These could form a single common library, or two separate common libraries.
## Decision
There will be a separate library for common and executor-base.
The libraries share different type of code, and have different reasons to change. It would not make sense to have the shared code from executors in every other service which needs access to other shared code. Services that use the code in the common library should not need to be dependent in any way on the executor-base.
## Consequences
Changes impact fewer services. However, this decision will increase the number of service dependencies and therefore increase complexity in managing those dependencies.

View File

@ -0,0 +1,23 @@
# 10. Single ownership for services
Date: 2021-10-18
## Status
Accepted
## Context
There are generally three options for persisting data. Single ownership, joint ownership, or common ownership.
## Decision
We will go for single ownership for all our databases. That is, each domain/service that will persist data will have its own database of which that service will be the sole owner. Any service that wants to write data to or read data from a database other than its own will have to go through the database's owner.
Having single ownership preserves the bounded contexts and allows each service to be its own architectural quantum. This allows the services to stay decoupled and therefore can allow us to also decouple the scope of the architectural characteristics of each bounded context. We can for example more easily scale each service up and down as we can also scale the databases (size, performance) for each service.
## Consequences
Having a distributed architecture and asynchronous communication along with single ownership will force us to rely on eventual consistency.
Moreover, this decision could negatively impact performance as we add latency to data access across bounded contexts.

View File

@ -0,0 +1,21 @@
# 11. Data access
Date: 2021-10-18
## Status
Accepted
## Context
Services can generally access data that they do not own via the Interservice Communication Pattern, the Column Schema Replication Pattern, the Replicated Caching Pattern, or the Data Domain Pattern
## Decision
Data access will follow the Interservice Communication Pattern.
All the information needed to process any given event in our system is almost always included in the event itself or already cached in the service from a previous event (e.g. the Executor registry being cached in the Roster and Auction House via new/removed executor events). Therefore, there are very few occasions where access to data from other services is needed. Since the Interservice Communication Pattern is the simplest, we will go for that.
## Consequences
There will be performance and fault tolerance issues when we need to access data from other services, but since does not often occur it will not have a significant effect on the system overall.

View File

@ -0,0 +1,27 @@
# 12. Separate service for Crawler
Date: 2021-10-18
## Status
Accepted
## Context
The Auction House Discovery Crawler (for simplicity referred to as the Crawler) continuously crawls external auction houses that we already know about to try and discover new auction houses. It maintains the information we have about other auction houses and notifies our Action House when it has discovered new information. The Crawler can either be a part of the Auction House service or be a standalone service.
## Decision
The Crawler will be implemented as a standalone service.
The most important reason for this is that it has different responsibilities to the Auction House. The Crawler has to continuously crawl for new information while the Auction House only needs to launch auctions for internal tasks and bid on external auctions.
Additionally, the services might have to scale differently depending on how rapidly we want to crawl new auction house information.
## Consequences
We will have to duplicate some code and data as the auction house information will be duplicated across both services (although the business logic for updating the information based on the timestamp will solely reside in the Crawler).
There will be an added latency to the communication between the two domains. However, since this information should not be overly time sensitive (a few ms should not matter), this should not impact overall system performance.

View File

@ -0,0 +1,21 @@
# 13. Use Hypermedia APIs when possible
Date: 2021-10-18
## Status
Accepted
## Context
When Executors need to communicate with external devices they can either hard code the API requests or it can discover them at run time via Semantic Hypermedia.
## Decision
We will discover the requests at run-time via the Semantic Hypermedia APIs.
This will decouples the Executors that offer physical services from the Web-enabled devices they use. This will make these Executors more resilient to change in the devices they use and therefore less likely to experience problems over time as the device APIs change.
## Consequences
The implementation of the run-time discovery is more complex, at least until the team has gained more experience in this domain.

View File

@ -0,0 +1,27 @@
# 13. Use Choreography For Task Execution Workflow
Date: 2021-12-10
## Status
Accepted
## Context
The Task Execution Workflow (workflow that takes a task from created to executed) is the main workflow in the whole TAPAS system. It can either be orchestrated, or choreographed. If it is choreographed then it should follow one of the following patterns: Front Controller Pattern, Stateless, or Stamp coupling.
## Decision
The Task Execution Workflow will be choreographed and will follow the Front Controller Pattern, storing the workflow state in the Task List.
The reason for why it should be choreographed is that this will optimise the for responsiveness, scalability, and fault tolerance. There are all driving architectural characteristics for our system.
Moreover, the workflow should utilise the Front Controller Pattern as this pattern fits the architecture naturally. The tasks in the Task List already have a Status property. This property can be used to keep track of the state of the task within the execution workflow. Moreover, since updating this state is already a part of the architecture, the system will not suffer greatly from the normal drawback of using this pattern (additional state property, increased communication overhead).
Using this pattern will make querying the state trivial since we just need to look up the status of the task in the Task List. Moreover, it makes the Task List a pseudo-orchestrator within our choreography which will reduce the complexity for error handling, as much of it can be added to the Task List. Lastly, since the Task List is accessible by the users that created the task, they can then make ultimate decisions for error handling. For example, when no executor has been found for a task (neither internal nor external) then the end user could either delete the task or ask for it to be re-run through the roster and auction house at a later date.
## Consequences
Making the Task Execution Workflow choreographed will make error handling more complex. For now it seems manageable, but if the complexity becomes to great we could implement the Workflow Event Pattern in order to mitigate the increased complexity.
Recoverability and state management might also suffer, but a good implementation of the Front Controller Pattern should mitigate more of the negatives for those aspects.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0grlf6g" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.8.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0"> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0grlf6g" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.8.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
<bpmn:collaboration id="Collaboration_0p9il2x"> <bpmn:collaboration id="Collaboration_0p9il2x">
<bpmn:participant id="Participant_06nkquo" name="Tasklist Service" processRef="Process_12mqbju" /> <bpmn:participant id="Participant_06nkquo" name="Tasklist Service" processRef="Process_12mqbju" />
<bpmn:participant id="Participant_016oseg" name="Roster Service" processRef="Process_1s4t9qw" /> <bpmn:participant id="Participant_016oseg" name="Roster Service" processRef="Process_1s4t9qw" />
@ -138,9 +138,6 @@
<bpmn:outgoing>Flow_19flgkm</bpmn:outgoing> <bpmn:outgoing>Flow_19flgkm</bpmn:outgoing>
<bpmn:outgoing>Flow_1ufcjqo</bpmn:outgoing> <bpmn:outgoing>Flow_1ufcjqo</bpmn:outgoing>
</bpmn:exclusiveGateway> </bpmn:exclusiveGateway>
<bpmn:task id="Activity_0kt4ce0" name="Check if task is getting executed">
<bpmn:outgoing>Flow_0yt3znc</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_1rxws1j" name="Yes" sourceRef="Gateway_1apm7h8" targetRef="Activity_0a4kzh7" /> <bpmn:sequenceFlow id="Flow_1rxws1j" name="Yes" sourceRef="Gateway_1apm7h8" targetRef="Activity_0a4kzh7" />
<bpmn:sequenceFlow id="Flow_1oy6e8u" name="No" sourceRef="Gateway_1apm7h8" targetRef="Activity_1v2mt3m" /> <bpmn:sequenceFlow id="Flow_1oy6e8u" name="No" sourceRef="Gateway_1apm7h8" targetRef="Activity_1v2mt3m" />
<bpmn:sequenceFlow id="Flow_1urp3d2" sourceRef="Activity_0x47yx9" targetRef="Gateway_1apm7h8" /> <bpmn:sequenceFlow id="Flow_1urp3d2" sourceRef="Activity_0x47yx9" targetRef="Gateway_1apm7h8" />
@ -151,6 +148,14 @@
<bpmn:sequenceFlow id="Flow_19flgkm" name="Yes" sourceRef="Gateway_1q5k5mf" targetRef="Activity_0trqd0z" /> <bpmn:sequenceFlow id="Flow_19flgkm" name="Yes" sourceRef="Gateway_1q5k5mf" targetRef="Activity_0trqd0z" />
<bpmn:sequenceFlow id="Flow_1ufcjqo" name="No" sourceRef="Gateway_1q5k5mf" targetRef="Activity_09ys1a2" /> <bpmn:sequenceFlow id="Flow_1ufcjqo" name="No" sourceRef="Gateway_1q5k5mf" targetRef="Activity_09ys1a2" />
<bpmn:sequenceFlow id="Flow_0yt3znc" sourceRef="Activity_0kt4ce0" targetRef="Gateway_1q5k5mf" /> <bpmn:sequenceFlow id="Flow_0yt3znc" sourceRef="Activity_0kt4ce0" targetRef="Gateway_1q5k5mf" />
<bpmn:sequenceFlow id="Flow_0k28pj4" sourceRef="Activity_0kt4ce0" targetRef="Activity_0aut87q" />
<bpmn:task id="Activity_0kt4ce0" name="Check if task is getting executed">
<bpmn:outgoing>Flow_0yt3znc</bpmn:outgoing>
<bpmn:outgoing>Flow_0k28pj4</bpmn:outgoing>
</bpmn:task>
<bpmn:task id="Activity_0aut87q" name="Check if task is auctioned">
<bpmn:incoming>Flow_0k28pj4</bpmn:incoming>
</bpmn:task>
</bpmn:process> </bpmn:process>
<bpmn:process id="Process_0bur8vc" isExecutable="false"> <bpmn:process id="Process_0bur8vc" isExecutable="false">
<bpmn:task id="Activity_14xojfa" name="Adds executor to pool" /> <bpmn:task id="Activity_14xojfa" name="Adds executor to pool" />
@ -315,56 +320,109 @@
</bpmn:process> </bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1"> <bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0p9il2x"> <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0p9il2x">
<bpmndi:BPMNShape id="Participant_06nkquo_di" bpmnElement="Participant_06nkquo" isHorizontal="true">
<dc:Bounds x="160" y="80" width="2030" height="420" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_04yudlk_di" bpmnElement="Flow_04yudlk" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="258" y="390" />
<di:waypoint x="320" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_020rl47_di" bpmnElement="Flow_020rl47">
<di:waypoint x="258" y="300" />
<di:waypoint x="320" y="300" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0q27k8x_di" bpmnElement="Flow_0q27k8x">
<di:waypoint x="2020" y="390" />
<di:waypoint x="2020" y="310" />
<di:waypoint x="1610" y="310" />
<di:waypoint x="1610" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1x5tn8d_di" bpmnElement="Flow_1x5tn8d">
<di:waypoint x="1750" y="430" />
<di:waypoint x="1660" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jdop0s_di" bpmnElement="Flow_0jdop0s">
<di:waypoint x="1415" y="430" />
<di:waypoint x="1360" y="430" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1380" y="412" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0t619kx_di" bpmnElement="Flow_0t619kx">
<di:waypoint x="1440" y="405" />
<di:waypoint x="1440" y="360" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1446" y="380" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0p2cvuq_di" bpmnElement="Flow_0p2cvuq">
<di:waypoint x="1560" y="430" />
<di:waypoint x="1465" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0drypjw_di" bpmnElement="Flow_0drypjw">
<di:waypoint x="258" y="180" />
<di:waypoint x="400" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Activity_0fiiuon_di" bpmnElement="Activity_0fiiuon">
<dc:Bounds x="1750" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01iahon_di" bpmnElement="Activity_01iahon">
<dc:Bounds x="1970" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1e01yys_di" bpmnElement="Activity_1e01yys">
<dc:Bounds x="1560" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1b7afz7_di" bpmnElement="Gateway_1b7afz7" isMarkerVisible="true">
<dc:Bounds x="1415" y="405" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1c318b6_di" bpmnElement="Activity_1c318b6">
<dc:Bounds x="1390" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0oro919_di" bpmnElement="Activity_0oro919">
<dc:Bounds x="1260" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1jq7hnv_di" bpmnElement="Activity_1jq7hnv" bioc:stroke="black" bioc:fill="white">
<dc:Bounds x="320" y="260" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0qpc9r0_di" bpmnElement="Activity_0qpc9r0">
<dc:Bounds x="1060" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_11958sp_di" bpmnElement="Activity_11958sp">
<dc:Bounds x="920" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0phm5et_di" bpmnElement="Activity_0phm5et" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="1190" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_04ek3w5_di" bpmnElement="Event_04ek3w5">
<dc:Bounds x="222" y="162" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="197" y="205" width="86" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_15jcufk_di" bpmnElement="Event_15jcufk">
<dc:Bounds x="222" y="282" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="208" y="325" width="65" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_099rjfn_di" bpmnElement="Event_099rjfn" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="222" y="372" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="200" y="415" width="82" height="80" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_13xp2vr_di" bpmnElement="Activity_13xp2vr">
<dc:Bounds x="400" y="140" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0jzf0yv_di" bpmnElement="Activity_0jzf0yv" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="320" y="350" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_016oseg_di" bpmnElement="Participant_016oseg" isHorizontal="true"> <bpmndi:BPMNShape id="Participant_016oseg_di" bpmnElement="Participant_016oseg" isHorizontal="true">
<dc:Bounds x="160" y="560" width="1950" height="410" /> <dc:Bounds x="160" y="560" width="1950" height="410" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1rxws1j_di" bpmnElement="Flow_1rxws1j"> <bpmndi:BPMNEdge id="Flow_0yt3znc_di" bpmnElement="Flow_0yt3znc">
<di:waypoint x="1635" y="800" /> <di:waypoint x="570" y="610" />
<di:waypoint x="1750" y="800" /> <di:waypoint x="645" y="610" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1684" y="782" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oy6e8u_di" bpmnElement="Flow_1oy6e8u">
<di:waypoint x="1610" y="825" />
<di:waypoint x="1610" y="880" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1618" y="843" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1urp3d2_di" bpmnElement="Flow_1urp3d2">
<di:waypoint x="1490" y="800" />
<di:waypoint x="1585" y="800" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_01pbz6s_di" bpmnElement="Flow_01pbz6s">
<di:waypoint x="830" y="815" />
<di:waypoint x="830" y="900" />
<di:waypoint x="950" y="900" />
<bpmndi:BPMNLabel>
<dc:Bounds x="882" y="883" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_159tlyd_di" bpmnElement="Flow_159tlyd">
<di:waypoint x="855" y="790" />
<di:waypoint x="950" y="790" />
<bpmndi:BPMNLabel>
<dc:Bounds x="894" y="772" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1rie16h_di" bpmnElement="Flow_1rie16h">
<di:waypoint x="570" y="790" />
<di:waypoint x="805" y="790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0z1xo5y_di" bpmnElement="Flow_0z1xo5y">
<di:waypoint x="420" y="790" />
<di:waypoint x="470" y="790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19flgkm_di" bpmnElement="Flow_19flgkm">
<di:waypoint x="695" y="610" />
<di:waypoint x="790" y="610" />
<bpmndi:BPMNLabel>
<dc:Bounds x="721" y="589" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ufcjqo_di" bpmnElement="Flow_1ufcjqo"> <bpmndi:BPMNEdge id="Flow_1ufcjqo_di" bpmnElement="Flow_1ufcjqo">
<di:waypoint x="670" y="635" /> <di:waypoint x="670" y="635" />
@ -374,9 +432,58 @@
<dc:Bounds x="722" y="683" width="15" height="14" /> <dc:Bounds x="722" y="683" width="15" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0yt3znc_di" bpmnElement="Flow_0yt3znc"> <bpmndi:BPMNEdge id="Flow_19flgkm_di" bpmnElement="Flow_19flgkm">
<di:waypoint x="580" y="610" /> <di:waypoint x="695" y="610" />
<di:waypoint x="645" y="610" /> <di:waypoint x="790" y="610" />
<bpmndi:BPMNLabel>
<dc:Bounds x="721" y="589" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0z1xo5y_di" bpmnElement="Flow_0z1xo5y">
<di:waypoint x="420" y="790" />
<di:waypoint x="470" y="790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1rie16h_di" bpmnElement="Flow_1rie16h">
<di:waypoint x="570" y="790" />
<di:waypoint x="805" y="790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_159tlyd_di" bpmnElement="Flow_159tlyd">
<di:waypoint x="855" y="790" />
<di:waypoint x="950" y="790" />
<bpmndi:BPMNLabel>
<dc:Bounds x="894" y="772" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_01pbz6s_di" bpmnElement="Flow_01pbz6s">
<di:waypoint x="830" y="815" />
<di:waypoint x="830" y="900" />
<di:waypoint x="950" y="900" />
<bpmndi:BPMNLabel>
<dc:Bounds x="882" y="883" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1urp3d2_di" bpmnElement="Flow_1urp3d2">
<di:waypoint x="1490" y="800" />
<di:waypoint x="1585" y="800" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oy6e8u_di" bpmnElement="Flow_1oy6e8u">
<di:waypoint x="1610" y="825" />
<di:waypoint x="1610" y="880" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1618" y="843" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1rxws1j_di" bpmnElement="Flow_1rxws1j">
<di:waypoint x="1635" y="800" />
<di:waypoint x="1750" y="800" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1684" y="782" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0k28pj4_di" bpmnElement="Flow_0k28pj4" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="520" y="650" />
<di:waypoint x="520" y="700" />
<di:waypoint x="550" y="700" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Gateway_0sqs3j7_di" bpmnElement="Gateway_0sqs3j7" isMarkerVisible="true"> <bpmndi:BPMNShape id="Gateway_0sqs3j7_di" bpmnElement="Gateway_0sqs3j7" isMarkerVisible="true">
<dc:Bounds x="805" y="765" width="50" height="50" /> <dc:Bounds x="805" y="765" width="50" height="50" />
@ -424,7 +531,10 @@
<dc:Bounds x="645" y="585" width="50" height="50" /> <dc:Bounds x="645" y="585" width="50" height="50" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0kt4ce0_di" bpmnElement="Activity_0kt4ce0"> <bpmndi:BPMNShape id="Activity_0kt4ce0_di" bpmnElement="Activity_0kt4ce0">
<dc:Bounds x="480" y="570" width="100" height="80" /> <dc:Bounds x="470" y="570" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0aut87q_di" bpmnElement="Activity_0aut87q" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="550" y="660" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_11sfrgz_di" bpmnElement="Participant_11sfrgz" isHorizontal="true"> <bpmndi:BPMNShape id="Participant_11sfrgz_di" bpmnElement="Participant_11sfrgz" isHorizontal="true">
<dc:Bounds x="160" y="1040" width="820" height="220" /> <dc:Bounds x="160" y="1040" width="820" height="220" />
@ -438,32 +548,37 @@
<bpmndi:BPMNShape id="Participant_1x7nd70_di" bpmnElement="Participant_1x7nd70" isHorizontal="true"> <bpmndi:BPMNShape id="Participant_1x7nd70_di" bpmnElement="Participant_1x7nd70" isHorizontal="true">
<dc:Bounds x="160" y="1330" width="1950" height="300" /> <dc:Bounds x="160" y="1330" width="1950" height="300" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1duwugb_di" bpmnElement="Flow_1duwugb"> <bpmndi:BPMNEdge id="Flow_1x2ab5q_di" bpmnElement="Flow_1x2ab5q">
<di:waypoint x="930" y="1385" /> <di:waypoint x="650" y="1575" />
<di:waypoint x="930" y="1360" /> <di:waypoint x="650" y="1610" />
<di:waypoint x="1420" y="1360" /> <di:waypoint x="440" y="1610" />
<di:waypoint x="1420" y="1400" /> <di:waypoint x="440" y="1590" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="961" y="1342" width="18" height="14" /> <dc:Bounds x="538" y="1592" width="15" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0b4g73l_di" bpmnElement="Flow_0b4g73l"> <bpmndi:BPMNEdge id="Flow_123h648_di" bpmnElement="Flow_123h648">
<di:waypoint x="930" y="1435" /> <di:waypoint x="650" y="1450" />
<di:waypoint x="930" y="1510" /> <di:waypoint x="650" y="1525" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0rh699s_di" bpmnElement="Flow_0rh699s">
<di:waypoint x="675" y="1550" />
<di:waypoint x="740" y="1550" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="938" y="1463" width="15" height="14" /> <dc:Bounds x="691" y="1532" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19m4xhk_di" bpmnElement="Flow_19m4xhk"> <bpmndi:BPMNEdge id="Flow_02boegx_di" bpmnElement="Flow_02boegx">
<di:waypoint x="810" y="1410" /> <di:waypoint x="268" y="1550" />
<di:waypoint x="905" y="1410" /> <di:waypoint x="390" y="1550" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ruufha_di" bpmnElement="Flow_0ruufha"> <bpmndi:BPMNEdge id="Flow_0wnknbm_di" bpmnElement="Flow_0wnknbm">
<di:waypoint x="1315" y="1440" /> <di:waypoint x="268" y="1390" />
<di:waypoint x="1400" y="1440" /> <di:waypoint x="390" y="1390" />
<bpmndi:BPMNLabel> </bpmndi:BPMNEdge>
<dc:Bounds x="1331" y="1422" width="18" height="14" /> <bpmndi:BPMNEdge id="Flow_0nuuhk7_di" bpmnElement="Flow_0nuuhk7">
</bpmndi:BPMNLabel> <di:waypoint x="1140" y="1440" />
<di:waypoint x="1265" y="1440" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_197gie6_di" bpmnElement="Flow_197gie6"> <bpmndi:BPMNEdge id="Flow_197gie6_di" bpmnElement="Flow_197gie6">
<di:waypoint x="1290" y="1465" /> <di:waypoint x="1290" y="1465" />
@ -473,36 +588,31 @@
<dc:Bounds x="1332" y="1533" width="15" height="14" /> <dc:Bounds x="1332" y="1533" width="15" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0nuuhk7_di" bpmnElement="Flow_0nuuhk7"> <bpmndi:BPMNEdge id="Flow_0ruufha_di" bpmnElement="Flow_0ruufha">
<di:waypoint x="1140" y="1440" /> <di:waypoint x="1315" y="1440" />
<di:waypoint x="1265" y="1440" /> <di:waypoint x="1400" y="1440" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wnknbm_di" bpmnElement="Flow_0wnknbm">
<di:waypoint x="268" y="1390" />
<di:waypoint x="390" y="1390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_02boegx_di" bpmnElement="Flow_02boegx">
<di:waypoint x="268" y="1550" />
<di:waypoint x="390" y="1550" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0rh699s_di" bpmnElement="Flow_0rh699s">
<di:waypoint x="675" y="1550" />
<di:waypoint x="740" y="1550" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="691" y="1532" width="18" height="14" /> <dc:Bounds x="1331" y="1422" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_123h648_di" bpmnElement="Flow_123h648"> <bpmndi:BPMNEdge id="Flow_19m4xhk_di" bpmnElement="Flow_19m4xhk">
<di:waypoint x="650" y="1450" /> <di:waypoint x="810" y="1410" />
<di:waypoint x="650" y="1525" /> <di:waypoint x="905" y="1410" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1x2ab5q_di" bpmnElement="Flow_1x2ab5q"> <bpmndi:BPMNEdge id="Flow_0b4g73l_di" bpmnElement="Flow_0b4g73l">
<di:waypoint x="650" y="1575" /> <di:waypoint x="930" y="1435" />
<di:waypoint x="650" y="1610" /> <di:waypoint x="930" y="1510" />
<di:waypoint x="440" y="1610" />
<di:waypoint x="440" y="1590" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="538" y="1592" width="15" height="14" /> <dc:Bounds x="938" y="1463" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1duwugb_di" bpmnElement="Flow_1duwugb">
<di:waypoint x="930" y="1385" />
<di:waypoint x="930" y="1360" />
<di:waypoint x="1420" y="1360" />
<di:waypoint x="1420" y="1400" />
<bpmndi:BPMNLabel>
<dc:Bounds x="961" y="1342" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Gateway_0nyndyn_di" bpmnElement="Gateway_0nyndyn" isMarkerVisible="true"> <bpmndi:BPMNShape id="Gateway_0nyndyn_di" bpmnElement="Gateway_0nyndyn" isMarkerVisible="true">
@ -559,22 +669,45 @@
<bpmndi:BPMNShape id="Participant_1ggu0ij_di" bpmnElement="Participant_1ggu0ij" isHorizontal="true"> <bpmndi:BPMNShape id="Participant_1ggu0ij_di" bpmnElement="Participant_1ggu0ij" isHorizontal="true">
<dc:Bounds x="160" y="1690" width="1950" height="380" /> <dc:Bounds x="160" y="1690" width="1950" height="380" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1c284l8_di" bpmnElement="Flow_1c284l8"> <bpmndi:BPMNEdge id="Flow_1v97ns8_di" bpmnElement="Flow_1v97ns8" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1490" y="1790" /> <di:waypoint x="1650" y="1935" />
<di:waypoint x="1600" y="1790" /> <di:waypoint x="1650" y="2010" />
<di:waypoint x="1590" y="2010" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="1502" y="1756" width="77" height="27" /> <dc:Bounds x="1605" y="1988" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wnl1dp_di" bpmnElement="Flow_1wnl1dp"> <bpmndi:BPMNEdge id="Flow_0xfuedn_di" bpmnElement="Flow_0xfuedn" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="520" y="1790" /> <di:waypoint x="1790" y="1910" />
<di:waypoint x="595" y="1790" /> <di:waypoint x="1675" y="1910" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_018ptp2_di" bpmnElement="Flow_018ptp2"> <bpmndi:BPMNEdge id="Flow_1iyxahk_di" bpmnElement="Flow_1iyxahk" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="620" y="1815" /> <di:waypoint x="1625" y="1910" />
<di:waypoint x="620" y="1890" /> <di:waypoint x="1350" y="1910" />
<di:waypoint x="1350" y="1830" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="632" y="1850" width="15" height="14" /> <dc:Bounds x="1570" y="1893" width="46" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1se7kv7_di" bpmnElement="Flow_1se7kv7" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1840" y="1815" />
<di:waypoint x="1840" y="1870" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1848" y="1839" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zar2lz_di" bpmnElement="Flow_0zar2lz" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1865" y="1790" />
<di:waypoint x="1950" y="1790" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1884" y="1773" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04emv5h_di" bpmnElement="Flow_04emv5h" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1700" y="1790" />
<di:waypoint x="1815" y="1790" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1717" y="1772" width="82" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0mdt1ms_di" bpmnElement="Flow_0mdt1ms"> <bpmndi:BPMNEdge id="Flow_0mdt1ms_di" bpmnElement="Flow_0mdt1ms">
@ -584,45 +717,22 @@
<dc:Bounds x="669" y="1772" width="18" height="14" /> <dc:Bounds x="669" y="1772" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04emv5h_di" bpmnElement="Flow_04emv5h"> <bpmndi:BPMNEdge id="Flow_018ptp2_di" bpmnElement="Flow_018ptp2">
<di:waypoint x="1700" y="1790" /> <di:waypoint x="620" y="1815" />
<di:waypoint x="1815" y="1790" /> <di:waypoint x="620" y="1890" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="1717" y="1772" width="82" height="14" /> <dc:Bounds x="632" y="1850" width="15" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zar2lz_di" bpmnElement="Flow_0zar2lz"> <bpmndi:BPMNEdge id="Flow_1wnl1dp_di" bpmnElement="Flow_1wnl1dp">
<di:waypoint x="1865" y="1790" /> <di:waypoint x="520" y="1790" />
<di:waypoint x="1950" y="1790" /> <di:waypoint x="595" y="1790" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1884" y="1773" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1se7kv7_di" bpmnElement="Flow_1se7kv7"> <bpmndi:BPMNEdge id="Flow_1c284l8_di" bpmnElement="Flow_1c284l8">
<di:waypoint x="1840" y="1815" /> <di:waypoint x="1490" y="1790" />
<di:waypoint x="1840" y="1870" /> <di:waypoint x="1600" y="1790" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="1848" y="1839" width="15" height="14" /> <dc:Bounds x="1502" y="1756" width="77" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1iyxahk_di" bpmnElement="Flow_1iyxahk">
<di:waypoint x="1625" y="1910" />
<di:waypoint x="1350" y="1910" />
<di:waypoint x="1350" y="1830" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1570" y="1893" width="46" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0xfuedn_di" bpmnElement="Flow_0xfuedn">
<di:waypoint x="1790" y="1910" />
<di:waypoint x="1675" y="1910" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1v97ns8_di" bpmnElement="Flow_1v97ns8">
<di:waypoint x="1650" y="1935" />
<di:waypoint x="1650" y="2010" />
<di:waypoint x="1590" y="2010" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1605" y="1988" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Activity_0nk3dyd_di" bpmnElement="Activity_0nk3dyd"> <bpmndi:BPMNShape id="Activity_0nk3dyd_di" bpmnElement="Activity_0nk3dyd">
@ -652,137 +762,32 @@
<bpmndi:BPMNShape id="Activity_0l3m7jz_di" bpmnElement="Activity_0l3m7jz"> <bpmndi:BPMNShape id="Activity_0l3m7jz_di" bpmnElement="Activity_0l3m7jz">
<dc:Bounds x="1600" y="1750" width="100" height="80" /> <dc:Bounds x="1600" y="1750" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1glhpou_di" bpmnElement="Gateway_1glhpou" isMarkerVisible="true"> <bpmndi:BPMNShape id="Gateway_1glhpou_di" bpmnElement="Gateway_1glhpou" isMarkerVisible="true" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="1815" y="1765" width="50" height="50" /> <dc:Bounds x="1815" y="1765" width="50" height="50" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0qk4791_di" bpmnElement="Activity_0qk4791"> <bpmndi:BPMNShape id="Activity_0qk4791_di" bpmnElement="Activity_0qk4791" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="1790" y="1870" width="100" height="80" /> <dc:Bounds x="1790" y="1870" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_01fwgfy_di" bpmnElement="Gateway_01fwgfy" isMarkerVisible="true"> <bpmndi:BPMNShape id="Gateway_01fwgfy_di" bpmnElement="Gateway_01fwgfy" isMarkerVisible="true" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="1625" y="1885" width="50" height="50" /> <dc:Bounds x="1625" y="1885" width="50" height="50" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1ahl6bl_di" bpmnElement="Activity_1ahl6bl"> <bpmndi:BPMNShape id="Activity_1ahl6bl_di" bpmnElement="Activity_1ahl6bl">
<dc:Bounds x="1950" y="1750" width="100" height="80" /> <dc:Bounds x="1950" y="1750" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0at6kvd_di" bpmnElement="Activity_0at6kvd"> <bpmndi:BPMNShape id="Activity_0at6kvd_di" bpmnElement="Activity_0at6kvd" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<dc:Bounds x="1490" y="1970" width="100" height="80" /> <dc:Bounds x="1490" y="1970" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0123dae_di" bpmnElement="Activity_0123dae"> <bpmndi:BPMNShape id="Activity_0123dae_di" bpmnElement="Activity_0123dae">
<dc:Bounds x="1270" y="1750" width="100" height="80" /> <dc:Bounds x="1270" y="1750" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_06nkquo_di" bpmnElement="Participant_06nkquo" isHorizontal="true">
<dc:Bounds x="160" y="80" width="2030" height="420" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0drypjw_di" bpmnElement="Flow_0drypjw">
<di:waypoint x="258" y="180" />
<di:waypoint x="400" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0p2cvuq_di" bpmnElement="Flow_0p2cvuq">
<di:waypoint x="1560" y="430" />
<di:waypoint x="1465" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0t619kx_di" bpmnElement="Flow_0t619kx">
<di:waypoint x="1440" y="405" />
<di:waypoint x="1440" y="360" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1446" y="380" width="18" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jdop0s_di" bpmnElement="Flow_0jdop0s">
<di:waypoint x="1415" y="430" />
<di:waypoint x="1360" y="430" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1380" y="412" width="15" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1x5tn8d_di" bpmnElement="Flow_1x5tn8d">
<di:waypoint x="1750" y="430" />
<di:waypoint x="1660" y="430" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0q27k8x_di" bpmnElement="Flow_0q27k8x">
<di:waypoint x="2020" y="390" />
<di:waypoint x="2020" y="310" />
<di:waypoint x="1610" y="310" />
<di:waypoint x="1610" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_020rl47_di" bpmnElement="Flow_020rl47">
<di:waypoint x="258" y="300" />
<di:waypoint x="320" y="300" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04yudlk_di" bpmnElement="Flow_04yudlk">
<di:waypoint x="258" y="390" />
<di:waypoint x="320" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Activity_0fiiuon_di" bpmnElement="Activity_0fiiuon">
<dc:Bounds x="1750" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01iahon_di" bpmnElement="Activity_01iahon">
<dc:Bounds x="1970" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1e01yys_di" bpmnElement="Activity_1e01yys">
<dc:Bounds x="1560" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1b7afz7_di" bpmnElement="Gateway_1b7afz7" isMarkerVisible="true">
<dc:Bounds x="1415" y="405" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1c318b6_di" bpmnElement="Activity_1c318b6">
<dc:Bounds x="1390" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0oro919_di" bpmnElement="Activity_0oro919">
<dc:Bounds x="1260" y="390" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1jq7hnv_di" bpmnElement="Activity_1jq7hnv">
<dc:Bounds x="320" y="260" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0qpc9r0_di" bpmnElement="Activity_0qpc9r0">
<dc:Bounds x="1060" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_11958sp_di" bpmnElement="Activity_11958sp">
<dc:Bounds x="920" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0phm5et_di" bpmnElement="Activity_0phm5et">
<dc:Bounds x="1190" y="280" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_04ek3w5_di" bpmnElement="Event_04ek3w5">
<dc:Bounds x="222" y="162" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="197" y="205" width="86" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_15jcufk_di" bpmnElement="Event_15jcufk">
<dc:Bounds x="222" y="282" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="208" y="325" width="65" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_099rjfn_di" bpmnElement="Event_099rjfn">
<dc:Bounds x="222" y="372" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="200" y="415" width="82" height="80" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_13xp2vr_di" bpmnElement="Activity_13xp2vr">
<dc:Bounds x="400" y="140" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0jzf0yv_di" bpmnElement="Activity_0jzf0yv">
<dc:Bounds x="320" y="350" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1d39i1c_di" bpmnElement="Participant_1d39i1c" isHorizontal="true"> <bpmndi:BPMNShape id="Participant_1d39i1c_di" bpmnElement="Participant_1d39i1c" isHorizontal="true">
<dc:Bounds x="160" y="2140" width="2100" height="300" /> <dc:Bounds x="160" y="2140" width="2100" height="300" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_13wk2ub_di" bpmnElement="Flow_13wk2ub"> <bpmndi:BPMNEdge id="Flow_1xk3spy_di" bpmnElement="Flow_1xk3spy">
<di:waypoint x="2050" y="2260" /> <di:waypoint x="910" y="2295" />
<di:waypoint x="2120" y="2260" /> <di:waypoint x="910" y="2340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zwkg3r_di" bpmnElement="Flow_0zwkg3r">
<di:waypoint x="268" y="2260" />
<di:waypoint x="420" y="2260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08pa8sd_di" bpmnElement="Flow_08pa8sd">
<di:waypoint x="810" y="2270" />
<di:waypoint x="885" y="2270" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="828" y="2252" width="39" height="14" /> <dc:Bounds x="918" y="2303" width="15" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0n0c3ba_di" bpmnElement="Flow_0n0c3ba"> <bpmndi:BPMNEdge id="Flow_0n0c3ba_di" bpmnElement="Flow_0n0c3ba">
@ -792,13 +797,21 @@
<dc:Bounds x="954" y="2249" width="18" height="14" /> <dc:Bounds x="954" y="2249" width="18" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xk3spy_di" bpmnElement="Flow_1xk3spy"> <bpmndi:BPMNEdge id="Flow_08pa8sd_di" bpmnElement="Flow_08pa8sd">
<di:waypoint x="910" y="2295" /> <di:waypoint x="810" y="2270" />
<di:waypoint x="910" y="2340" /> <di:waypoint x="885" y="2270" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="918" y="2303" width="15" height="14" /> <dc:Bounds x="828" y="2252" width="39" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zwkg3r_di" bpmnElement="Flow_0zwkg3r">
<di:waypoint x="268" y="2260" />
<di:waypoint x="420" y="2260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_13wk2ub_di" bpmnElement="Flow_13wk2ub">
<di:waypoint x="2050" y="2260" />
<di:waypoint x="2120" y="2260" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_1pt77mj_di" bpmnElement="Event_1pt77mj"> <bpmndi:BPMNShape id="Event_1pt77mj_di" bpmnElement="Event_1pt77mj">
<dc:Bounds x="232" y="2242" width="36" height="36" /> <dc:Bounds x="232" y="2242" width="36" height="36" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
@ -829,6 +842,12 @@
<bpmndi:BPMNShape id="Activity_0x8r7k7_di" bpmnElement="Activity_0x8r7k7"> <bpmndi:BPMNShape id="Activity_0x8r7k7_di" bpmnElement="Activity_0x8r7k7">
<dc:Bounds x="1270" y="2220" width="100" height="80" /> <dc:Bounds x="1270" y="2220" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_14smr2g_di" bpmnElement="Flow_14smr2g">
<di:waypoint x="450" y="220" />
<di:waypoint x="450" y="610" />
<di:waypoint x="390" y="610" />
<di:waypoint x="390" y="750" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0z85582_di" bpmnElement="Flow_0z85582"> <bpmndi:BPMNEdge id="Flow_0z85582_di" bpmnElement="Flow_0z85582">
<di:waypoint x="1050" y="790" /> <di:waypoint x="1050" y="790" />
<di:waypoint x="1090" y="790" /> <di:waypoint x="1090" y="790" />
@ -837,12 +856,6 @@
<dc:Bounds x="1000" y="1130" width="81" height="40" /> <dc:Bounds x="1000" y="1130" width="81" height="40" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wwbgs7_di" bpmnElement="Flow_0wwbgs7">
<di:waypoint x="1050" y="900" />
<di:waypoint x="1210" y="900" />
<di:waypoint x="1210" y="1790" />
<di:waypoint x="1270" y="1790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_02hwoew_di" bpmnElement="Flow_02hwoew"> <bpmndi:BPMNEdge id="Flow_02hwoew_di" bpmnElement="Flow_02hwoew">
<di:waypoint x="1450" y="1400" /> <di:waypoint x="1450" y="1400" />
<di:waypoint x="1450" y="840" /> <di:waypoint x="1450" y="840" />
@ -861,6 +874,13 @@
<dc:Bounds x="1705" y="582" width="69" height="27" /> <dc:Bounds x="1705" y="582" width="69" height="27" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1gknjes_di" bpmnElement="Flow_1gknjes">
<di:waypoint x="2020" y="760" />
<di:waypoint x="2020" y="470" />
<bpmndi:BPMNLabel>
<dc:Bounds x="2029" y="593" width="82" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0fihv5s_di" bpmnElement="Flow_0fihv5s"> <bpmndi:BPMNEdge id="Flow_0fihv5s_di" bpmnElement="Flow_0fihv5s">
<di:waypoint x="1850" y="1440" /> <di:waypoint x="1850" y="1440" />
<di:waypoint x="2020" y="1440" /> <di:waypoint x="2020" y="1440" />
@ -869,22 +889,20 @@
<dc:Bounds x="2036" y="1124" width="88" height="14" /> <dc:Bounds x="2036" y="1124" width="88" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1gknjes_di" bpmnElement="Flow_1gknjes"> <bpmndi:BPMNEdge id="Flow_1qv7s55_di" bpmnElement="Flow_1qv7s55">
<di:waypoint x="2020" y="760" /> <di:waypoint x="440" y="1350" />
<di:waypoint x="2020" y="470" /> <di:waypoint x="440" y="1140" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>
<dc:Bounds x="2029" y="593" width="82" height="27" /> <dc:Bounds x="446" y="1280" width="68" height="40" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_14smr2g_di" bpmnElement="Flow_14smr2g"> <bpmndi:BPMNEdge id="Flow_1l0z3bv_di" bpmnElement="Flow_1l0z3bv">
<di:waypoint x="450" y="220" /> <di:waypoint x="490" y="1100" />
<di:waypoint x="450" y="610" /> <di:waypoint x="760" y="1100" />
<di:waypoint x="390" y="610" /> <di:waypoint x="760" y="1370" />
<di:waypoint x="390" y="750" /> <bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> <dc:Bounds x="777" y="1164" width="87" height="27" />
<bpmndi:BPMNEdge id="Flow_0zr6p4m_di" bpmnElement="Flow_0zr6p4m"> </bpmndi:BPMNLabel>
<di:waypoint x="350" y="430" />
<di:waypoint x="350" y="750" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1bx5dnk_di" bpmnElement="Flow_1bx5dnk"> <bpmndi:BPMNEdge id="Flow_1bx5dnk_di" bpmnElement="Flow_1bx5dnk">
<di:waypoint x="440" y="1060" /> <di:waypoint x="440" y="1060" />
@ -893,12 +911,89 @@
<dc:Bounds x="339" y="996" width="82" height="27" /> <dc:Bounds x="339" y="996" width="82" height="27" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wwbgs7_di" bpmnElement="Flow_0wwbgs7">
<di:waypoint x="1050" y="900" />
<di:waypoint x="1210" y="900" />
<di:waypoint x="1210" y="1790" />
<di:waypoint x="1270" y="1790" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_05s4fa6_di" bpmnElement="Flow_05s4fa6">
<di:waypoint x="1320" y="1830" />
<di:waypoint x="1320" y="2220" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1215" y="2080" width="90" height="40" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_009mww7_di" bpmnElement="Flow_009mww7">
<di:waypoint x="1370" y="2256" />
<di:waypoint x="1440" y="2256" />
<di:waypoint x="1440" y="1830" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0veeafb_di" bpmnElement="Flow_0veeafb">
<di:waypoint x="1950" y="2260" />
<di:waypoint x="1910" y="2260" />
<di:waypoint x="1910" y="430" />
<di:waypoint x="1850" y="430" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1835" y="2096" width="69" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ldoe50_di" bpmnElement="Flow_0ldoe50">
<di:waypoint x="470" y="2220" />
<di:waypoint x="470" y="1830" />
<bpmndi:BPMNLabel>
<dc:Bounds x="485" y="2080" width="90" height="40" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19w7sp3_di" bpmnElement="Flow_19w7sp3">
<di:waypoint x="390" y="1100" />
<di:waypoint x="340" y="1100" />
<di:waypoint x="340" y="1780" />
<di:waypoint x="310" y="1780" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0qw77lq_di" bpmnElement="Flow_0qw77lq">
<di:waypoint x="2170" y="2220" />
<di:waypoint x="2170" y="410" />
<di:waypoint x="2070" y="410" />
<bpmndi:BPMNLabel>
<dc:Bounds x="2179" y="2086" width="82" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0id432z_di" bpmnElement="Flow_0id432z">
<di:waypoint x="760" y="1830" />
<di:waypoint x="760" y="2230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vqoo2z_di" bpmnElement="Flow_1vqoo2z">
<di:waypoint x="1040" y="2230" />
<di:waypoint x="1040" y="1830" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ikgoz3_di" bpmnElement="Flow_1ikgoz3">
<di:waypoint x="1090" y="1790" />
<di:waypoint x="1170" y="1790" />
<di:waypoint x="1170" y="180" />
<di:waypoint x="500" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jo7zxr_di" bpmnElement="Flow_1jo7zxr">
<di:waypoint x="490" y="1550" />
<di:waypoint x="570" y="1550" />
<di:waypoint x="570" y="1230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zv6duf_di" bpmnElement="Flow_0zv6duf"> <bpmndi:BPMNEdge id="Flow_0zv6duf_di" bpmnElement="Flow_0zv6duf">
<di:waypoint x="570" y="1150" /> <di:waypoint x="570" y="1150" />
<di:waypoint x="570" y="1045" /> <di:waypoint x="570" y="1045" />
<di:waypoint x="580" y="1045" /> <di:waypoint x="580" y="1045" />
<di:waypoint x="580" y="940" /> <di:waypoint x="580" y="940" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oxbeps_di" bpmnElement="Flow_1oxbeps">
<di:waypoint x="520" y="1190" />
<di:waypoint x="370" y="1190" />
<di:waypoint x="370" y="1860" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0au0up3_di" bpmnElement="Flow_0au0up3" bioc:stroke="black" bioc:fill="white">
<di:waypoint x="420" y="300" />
<di:waypoint x="520" y="300" />
<di:waypoint x="520" y="570" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jvqtzp_di" bpmnElement="Flow_0jvqtzp"> <bpmndi:BPMNEdge id="Flow_0jvqtzp_di" bpmnElement="Flow_0jvqtzp">
<di:waypoint x="890" y="610" /> <di:waypoint x="890" y="610" />
<di:waypoint x="970" y="610" /> <di:waypoint x="970" y="610" />
@ -915,105 +1010,23 @@
<dc:Bounds x="1018" y="523" width="84" height="14" /> <dc:Bounds x="1018" y="523" width="84" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0au0up3_di" bpmnElement="Flow_0au0up3">
<di:waypoint x="420" y="300" />
<di:waypoint x="530" y="300" />
<di:waypoint x="530" y="570" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qv7s55_di" bpmnElement="Flow_1qv7s55">
<di:waypoint x="440" y="1350" />
<di:waypoint x="440" y="1140" />
<bpmndi:BPMNLabel>
<dc:Bounds x="446" y="1280" width="68" height="40" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1l0z3bv_di" bpmnElement="Flow_1l0z3bv">
<di:waypoint x="490" y="1100" />
<di:waypoint x="760" y="1100" />
<di:waypoint x="760" y="1370" />
<bpmndi:BPMNLabel>
<dc:Bounds x="777" y="1164" width="87" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19w7sp3_di" bpmnElement="Flow_19w7sp3">
<di:waypoint x="390" y="1100" />
<di:waypoint x="340" y="1100" />
<di:waypoint x="340" y="1780" />
<di:waypoint x="310" y="1780" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1jo7zxr_di" bpmnElement="Flow_1jo7zxr">
<di:waypoint x="490" y="1550" />
<di:waypoint x="570" y="1550" />
<di:waypoint x="570" y="1230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1oxbeps_di" bpmnElement="Flow_1oxbeps">
<di:waypoint x="520" y="1190" />
<di:waypoint x="370" y="1190" />
<di:waypoint x="370" y="1860" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_17w04go_di" bpmnElement="Flow_17w04go"> <bpmndi:BPMNEdge id="Flow_17w04go_di" bpmnElement="Flow_17w04go">
<di:waypoint x="620" y="1190" /> <di:waypoint x="620" y="1190" />
<di:waypoint x="650" y="1190" /> <di:waypoint x="650" y="1190" />
<di:waypoint x="650" y="1370" /> <di:waypoint x="650" y="1370" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ldoe50_di" bpmnElement="Flow_0ldoe50">
<di:waypoint x="470" y="2220" />
<di:waypoint x="470" y="1830" />
<bpmndi:BPMNLabel>
<dc:Bounds x="485" y="2080" width="90" height="40" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0id432z_di" bpmnElement="Flow_0id432z">
<di:waypoint x="760" y="1830" />
<di:waypoint x="760" y="2230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vqoo2z_di" bpmnElement="Flow_1vqoo2z">
<di:waypoint x="1040" y="2230" />
<di:waypoint x="1040" y="1830" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ikgoz3_di" bpmnElement="Flow_1ikgoz3">
<di:waypoint x="1090" y="1790" />
<di:waypoint x="1170" y="1790" />
<di:waypoint x="1170" y="180" />
<di:waypoint x="500" y="180" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_009mww7_di" bpmnElement="Flow_009mww7">
<di:waypoint x="1370" y="2256" />
<di:waypoint x="1440" y="2256" />
<di:waypoint x="1440" y="1830" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1lznexm_di" bpmnElement="Flow_1lznexm"> <bpmndi:BPMNEdge id="Flow_1lznexm_di" bpmnElement="Flow_1lznexm">
<di:waypoint x="2000" y="1830" /> <di:waypoint x="2000" y="1830" />
<di:waypoint x="2000" y="2220" /> <di:waypoint x="2000" y="2220" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0stgbfi_di" bpmnElement="Flow_0stgbfi"> <bpmndi:BPMNEdge id="Flow_0stgbfi_di" bpmnElement="Flow_0stgbfi" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1490" y="2010" /> <di:waypoint x="1490" y="2010" />
<di:waypoint x="1240" y="2010" /> <di:waypoint x="1240" y="2010" />
<di:waypoint x="1240" y="360" /> <di:waypoint x="1240" y="360" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_05s4fa6_di" bpmnElement="Flow_05s4fa6"> <bpmndi:BPMNEdge id="Flow_0zr6p4m_di" bpmnElement="Flow_0zr6p4m" bioc:stroke="rgb(229, 57, 53)" bioc:fill="rgb(255, 205, 210)">
<di:waypoint x="1320" y="1830" /> <di:waypoint x="350" y="430" />
<di:waypoint x="1320" y="2220" /> <di:waypoint x="350" y="750" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1215" y="2080" width="90" height="40" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0veeafb_di" bpmnElement="Flow_0veeafb">
<di:waypoint x="1950" y="2260" />
<di:waypoint x="1910" y="2260" />
<di:waypoint x="1910" y="430" />
<di:waypoint x="1850" y="430" />
<bpmndi:BPMNLabel>
<dc:Bounds x="1835" y="2096" width="69" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0qw77lq_di" bpmnElement="Flow_0qw77lq">
<di:waypoint x="2170" y="2220" />
<di:waypoint x="2170" y="410" />
<di:waypoint x="2070" y="410" />
<bpmndi:BPMNLabel>
<dc:Bounds x="2179" y="2086" width="82" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram> </bpmndi:BPMNDiagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -1,13 +0,0 @@
# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021)
FROM maven as build
COPY . /app
RUN mvn -f app/pom.xml --batch-mode --update-snapshots verify
FROM openjdk
COPY --from=build /app/target/app-0.1.0.jar ./app-0.1.0.jar
CMD java -jar app-0.1.0.jar

View File

@ -9,7 +9,7 @@ import java.util.Set;
public abstract class SelfValidating<T> { public abstract class SelfValidating<T> {
private Validator validator; private final Validator validator;
protected SelfValidating() { protected SelfValidating() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

View File

@ -1,19 +0,0 @@
# 1. Record architecture decisions
Date: 2021-10-18
## Status
Accepted
## Context
We need to record the architectural decisions made on this project.
## Decision
We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
## Consequences
See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).

View File

@ -1,21 +0,0 @@
# 2. Seperate service for Executor Pool
Date: 2021-11-21
## Status
Accepted
## Context
The Executor Pool has to keep track of which Executors are online and what task types they can execute. The Roster keeps track of tasks that need to be executed and assigns Executors to tasks. The Executor Pool functionalty could be implemented in a seperate service or as a part of the Roster.
## Decision
The executor pool will be implemented as a seperate service. (TODO decision might change. Need to reevaluate)
TODO explain why.
## Consequences
TODO

View File

@ -1,21 +0,0 @@
# 3. Separate service for the Roster
Date: 2021-11-21
## Status
Accepted
## Context
The roster acts as an orchestrator for the system. It communicates directly with the task list, the executors, the executor pool, and the auction house. It handles the assignment of a task to a corresponding and available executor, keeps track of all the connections between tasks and executors, and communicates the status of tasks and executors to other services.
## Decision
The Roster domain will be its own service.
The Roster service will be a central point in our application. It will have most of the workflow logic in it and will communicate with all the different services. Therefore, other services can focus on their business logic and be largely ignorant of the overall workflow.
The code of the assignment will change more often than the code of the other services, thus having the assignment service split from the other makes it more deployable.
## Consequences
Having this system as its own service will reduce the fault tolerance because the assignment service can be the single point of failure. We can mitigate this risk by implementing (server) replication and/or having an event driven communication with persisting messages. Therefore, all other services can run independently, and the assignment service can recover from a crash. Additionally, we need to ensure a high level of interoperability, since the roster has to communicate with all other parts of the system.

View File

@ -1,20 +0,0 @@
# 4. Separate service for the Task List
Date: 2021-11-21
## Status
Accepted
## Context
Tasks are created in the task list, and the status of each task (created, assigned, executing, executed) is tracked in the task list as well. The task list mainly communicates with the roster so that tasks can get assigned and the roster will give the task list feedback about the tasks status.
## Decision
The task list will be its own service.
The task list needs to scale based on the number of active users and the intensity of their activity at any time while the scaling of other parts of the system can be constrained by other factors.
## Consequences
Although having the task list as its own service might slightly increase the complexity of the system and decrease the testability, it also makes the system easier to deploy and protective of its data. However, to ensure that this data is always available and does not get lost, the task list needs to be able to recover all its data (the entire history of all tasks) in case it goes down.

View File

@ -1,20 +0,0 @@
# 5. Event driven communication
Date: 2021-10-18
## Status
Superceded by [8. Switch to an event-driven microservices architecture](0008-switch-to-an-event-driven-microservices-architecture.md)
## Context
Services need to be able to communicate with each other. Services need to be scalable and therefore multiple services will need to get the same messages. Most of the processes are about responding to events that are happening throughout the system.
## Decision
We will use mainly event driven communication.
## Consequences
Event driven communication will help use to create a system which has high scalability and elasticity. Through persisting messages, we will also reach way higher fault tolerance and recoverability.
Having an event driven communication, we can only guarantee eventual consistency.

View File

@ -1,20 +0,0 @@
# 6. One global database or one database per service
Date: 2021-10-18
## Status
Accepted
## Context
We can have one database for all services or each Microservice can have its own database.
## Decision
Each Microservice will have its own database.
The different services dont need to store a lot of similar data. Therefore, we can have every Microservice handle its own data. This also gives the advantage that every Microservice owns its own data and is also responsible for it. (Data ownership, Data responsibility).
## Consequences
Having one database per Microservice will lead to eventual consistency. Having an event driven communication we can use event-based synchronisation to keep the data in sync between the services, thus the individual services dont need to know about each other. To guarantee data consistency we can also use a pattern like sagas.

View File

@ -1,22 +0,0 @@
# 7. Seperate service for Auction House
Date: 2021-11-21
## Status
Accepted
## Context
The auction house is the service that can connect to other groups auction houses. If there is a task whose task type does not match that of our executors, the auction house can start an auction where other groups can bid on doing the task for us. Moreover, it can also bid on other groups auctions.
## Decision
The auction house will be its own service.
The auction house is the only part of our system that has external communication; therefore, it makes sense to have it as its own service, also to guarantee better deployability.
The auction house does not scale directly based on the number of tasks, but only the proportion which needs external executors. Moreover, there could be limits on the number of auctions that could be started. Therefore, the auction house scales differently to other services.
Moreover, having the auction house as its own service also improves the fault tolerance of our system.
## Consequences
Since the auction house will be a standalone service, we have to make sure that if it goes down, it can recover its data in some way (which auctions it has launched, which auctions it has placed bids on or even won, etc.). Even though the testability and latency of our system might worsen by having a separate service for the auction house, we can implement different kinds of communication for internal and external communication in a much easier way.

View File

@ -1,26 +0,0 @@
# 8. Switch to an event-driven microservices architecture
Date: 2021-11-21
## Status
Proposed
Supercedes [5. Event driven communication](0005-event-driven-communication.md) TODO Fix this. Should only supercede it if this has been accepted. This should also subercede 0013 - Microservice Architecture if accepted.
## Context
Our Tapas App is currently implemented based on a microservice architecture, where the services communicate synchronously via request-response. Each service encapsulates a different bounded context with different functional and non-functional requirements. Internal communication could also be done using asynchronous or event-driven communication.
## Decision
Pros:
Scalability: Different services within the Tapas app are not always able to scale at the same rate. For example, we could have thousands of users adding printing tasks at the same time, but maybe we only have one printer. In this scenario we might want to scale the task-list service up to handle the creation load, but scaling up the printing executor operates on a different time-scale (i.e. adding a printer takes time). Moreover, we could have a lot of new tasks coming in, most of which can be executed internally. In this case we want to be able to scale up the task list but might not need to scale up the auction house. Event-driven communication would decrease the coupling of services. Consequently, the scalability of individual services would be enhanced as they no longer depend on the scalability of other services. This improves the apps overall scalability. Since scalability is one of the systems top 3 -ility, this seems quite important.
Fault tolerance: Another of the systems top 3 -ilities is fault tolerance. We could have highly unstable IoT executors that fail often. This should not disrupt the systems overall operation. The decoupling facilitated by event-driven, asynchronous, communication ensures that when individual services go down, the impact of other services is limited and once they go back up then can recover the systems state from persisted messages.
Cons:
Error handling, workflow control, and event timing:
The aforementioned topics outline the drawbacks of event- driven architecture. These drawbacks can be mitigated by using an orchestrator (like we currently do with the roster) to orchestrate assignment of tasks, auctioning off tasks and error handling when executors fail. More research needed.
## Consequences
Consequences to be determined but would relate to the three concepts mentioned as cons.

View File

@ -1,19 +0,0 @@
# 9. common library for shared code
Date: 2021-11-21
## Status
Accepted
## Context
The numerous services that make up the Tapas app all have common, non-domain specific, functionality that can be shared via a common library, or be replicated in each service.
## Decision
Use a common code library for shared code which does not change frequently, but if it would be changed, would need to be changed everywhere.
## Consequences
Changes in the common code will most likely require multiple services to be redeployed. However, those services would most likely have to have been changed individually and redepolyed anyways. Another consequence is that versioning becomes more complicated.

View File

@ -1,20 +0,0 @@
# 10. executor base library
Date: 2021-11-21
## Status
Accepted
## Context
Executors all use the same logic to communicate with other services. This means that their code base is near identical except for the code that implements their specific task execution. The code that implements the executor's shared logic can either be implemented by a shared library, or replicated across all executors.
## Decision
Since all executors use the same logic to communicate with other services, any change to this logic would have to be made for every executor. We will therefore use a shared library for executors, called executor-base. The library includes all the shared logic which every executor needs.
Having this shared logic in a separate library makes it easy to change the common logic at one place. The code sharing happens at compile time, which reduces the chance of runtime errors, compared to other code sharing approaches. If other people need to create executors, they can just reference the executor-base library and implement the actual execution part. Therefore they dont need to worry about the the connection implementations to the Tapas system.
## Consequences
It becomes easier to change the way that the executors communicate with the rest of the system. Moreover, changes are less risky as they only need to be implemented once. It also becomes easier for other teams within the organisation to create executors as they can use the executor-base to implement the shared logic. However, using a shared library will increase the complexity of the executors. Also there needs to be a clear way to use proper versioning. Lastly, by using this library we might be making assumptions for future executors that might not hold. For example, if we want to create more lightweight executors for IoT devices we might need to create a separate base package (if the current one becomes too fat), so that the executors can stay lightweight and dont implement unused code.

View File

@ -1,19 +0,0 @@
# 11. seperation of common and executor-base library
Date: 2021-11-21
## Status
Accepted
## Context
We have two code sharing libraries, the executor-base and the common library. The executor-base implements shared logic that all executors need, but other services don't. The common library has much more wide reaching implementations, such as the implementation of the SelfValidating class. These could form a single common library, or two seperate common libraries.
## Decision
There will be a separate library for common and executor-base. The libraries share different type of code, and have different reasons to change. It would not make sense to have the shared code from executors in every other service which needs access to other shared code. Services that use the code in the common library should not need to be dependent in any way on the executor-base.
## Consequences
Changes impact fewer services. However, this decision will increase the number of service dependencies and therefore increase complexity in managing those dependencies.

View File

@ -1,23 +0,0 @@
# 13. microservice architecture
Date: 2021-12-02
## Status
Accepted
## Context
The system is made up of five distinct bounded contexts, namely the Task Domain, the Roster Domain, the Executor Pool Domain, the Executor Domain, and the Auction Domain. The way that these bounded contexts function together to fulfil the system requirements can be based on many different architectures. (Feedback needed. Should we name specific 'next-best' alternative architectures to compare, or just leave it as is since technically all architectures were considered)
## Decision
The system will follow the Microservice architecture.
Scalability and fault tolerance are two of the systems top 3 -ilities. Moreover, elasticity and evolvability are two of the systems other main -ilities. These are all non-functional requirements that the Microservice architecture excels at.
We do not expect to have a single monolithic database, so this is not a concern.
## Consequences
There is a considerable amount of communication between bounded contexts. This could cause responsiveness and performance issues due to added latency. This could therefore mean we would need to use asynchronous REST calls or publish-subscribe communication to mitigate these issues as much of the communication does not have to be synchronous.

View File

@ -1,19 +0,0 @@
# 14. data ownership
Date: 2021-12-02
## Status
Accepted
## Context
The issue motivating this decision, and any context that influences or constrains the decision.
## Decision
The change that we're proposing or have agreed to implement.
## Consequences
What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -72,15 +72,15 @@ services:
volumes: volumes:
- ./executor-computation/src:/opt/app/src - ./executor-computation/src:/opt/app/src
- ./executor-computation/target:/opt/app/target - ./executor-computation/target:/opt/app/target
executor-robot: # executor-robot:
container_name: executor-robot # container_name: executor-robot
build: # build:
context: "." # context: "."
dockerfile: "./executor-robot/Dockerfile" # dockerfile: "./executor-robot/Dockerfile"
target: development # target: development
ports: # ports:
- "8084:8084" # - "8084:8084"
- "5009:5005" # - "5009:5005"
volumes: # volumes:
- ./executor-robot/src:/opt/app/src # - ./executor-robot/src:/opt/app/src
- ./executor-robot/target:/opt/app/target # - ./executor-robot/target:/opt/app/target

View File

@ -1,73 +0,0 @@
package ch.unisg.executorbase.executor.adapter.out.web;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.application.port.out.GetAssignmentPort;
import ch.unisg.executorbase.executor.domain.ExecutorType;
import ch.unisg.executorbase.executor.domain.Task;
import org.json.JSONObject;
@Component
@Primary
public class GetAssignmentAdapter implements GetAssignmentPort {
String server = System.getenv("ROSTER_URI") == null ?
"http://localhost:8082" : System.getenv("ROSTER_URI");
Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName());
/**
* Requests a new task assignment
* @return the assigned task
* @see Task
**/
@Override
public Task getAssignment(ExecutorType executorType, ExecutorURI executorURI) {
String body = new JSONObject()
.put("executorType", executorType)
.put("executorURI", executorURI.getValue())
.toString();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/task/apply"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
try {
logger.info("Sending getAssignment Request");
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
logger.log(Level.INFO, "getAssignment request result:\n {0}", response.body());
if (response.body().equals("")) {
return null;
}
JSONObject responseBody = new JSONObject(response.body());
String inputData = responseBody.getString("inputData");
return new Task(responseBody.getString("taskID"), inputData);
} catch (InterruptedException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
return null;
}
}

View File

@ -1,19 +0,0 @@
package ch.unisg.executorbase.executor.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.common.validation.SelfValidating;
import ch.unisg.executorbase.executor.domain.ExecutorType;
import lombok.Value;
@Value
public class TaskAvailableCommand extends SelfValidating<TaskAvailableCommand> {
@NotNull
private final ExecutorType taskType;
public TaskAvailableCommand(ExecutorType taskType) {
this.taskType = taskType;
this.validateSelf();
}
}

View File

@ -1,5 +0,0 @@
package ch.unisg.executorbase.executor.application.port.in;
public interface TaskAvailableUseCase {
void newTaskAvailable(TaskAvailableCommand command);
}

View File

@ -1,7 +0,0 @@
package ch.unisg.executorbase.executor.application.port.out;
import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent;
public interface ExecutionFinishedEventPort {
void publishExecutionFinishedEvent(ExecutionFinishedEvent event);
}

View File

@ -1,9 +0,0 @@
package ch.unisg.executorbase.executor.application.port.out;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.domain.ExecutorType;
import ch.unisg.executorbase.executor.domain.Task;
public interface GetAssignmentPort {
Task getAssignment(ExecutorType executorType, ExecutorURI executorURI);
}

View File

@ -1,8 +0,0 @@
package ch.unisg.executorbase.executor.application.port.out;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.domain.ExecutorType;
public interface NotifyExecutorPoolPort {
boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType);
}

View File

@ -1,16 +0,0 @@
package ch.unisg.executorbase.executor.application.service;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.application.port.out.NotifyExecutorPoolPort;
import ch.unisg.executorbase.executor.domain.ExecutorType;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class NotifyExecutorPoolService {
private final NotifyExecutorPoolPort notifyExecutorPoolPort;
public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) {
return notifyExecutorPoolPort.notifyExecutorPool(executorURI, executorType);
}
}

View File

@ -1,20 +0,0 @@
package ch.unisg.executorbase.executor.application.service;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableCommand;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableUseCase;
import lombok.RequiredArgsConstructor;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Component
@Transactional
public class TaskAvailableService implements TaskAvailableUseCase {
@Override
public void newTaskAvailable(TaskAvailableCommand command) {
// Placeholder so spring can create a bean, implementation of this function is inside the executors
}
}

View File

@ -1,21 +0,0 @@
package ch.unisg.executorbase.executor.domain;
import lombok.Getter;
public class ExecutionFinishedEvent {
@Getter
private String taskID;
@Getter
private String outputData;
@Getter
private String status;
public ExecutionFinishedEvent(String taskID, String outputData, String status) {
this.taskID = taskID;
this.outputData = outputData;
this.status = status;
}
}

View File

@ -1,93 +0,0 @@
package ch.unisg.executorbase.executor.domain;
import java.util.logging.Level;
import java.util.logging.Logger;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.adapter.out.web.ExecutionFinishedEventAdapter;
import ch.unisg.executorbase.executor.adapter.out.web.GetAssignmentAdapter;
import ch.unisg.executorbase.executor.adapter.out.web.NotifyExecutorPoolAdapter;
import ch.unisg.executorbase.executor.application.port.out.ExecutionFinishedEventPort;
import ch.unisg.executorbase.executor.application.port.out.GetAssignmentPort;
import ch.unisg.executorbase.executor.application.port.out.NotifyExecutorPoolPort;
import ch.unisg.executorbase.executor.application.service.NotifyExecutorPoolService;
import lombok.Getter;
public abstract class ExecutorBase {
@Getter
private ExecutorURI executorURI;
@Getter
private ExecutorType executorType;
@Getter
private ExecutorStatus status;
// TODO Violation of the Dependency Inversion Principle?,
// TODO do this with only services
private final NotifyExecutorPoolPort notifyExecutorPoolPort = new NotifyExecutorPoolAdapter();
private final NotifyExecutorPoolService notifyExecutorPoolService = new NotifyExecutorPoolService(notifyExecutorPoolPort);
private final GetAssignmentPort getAssignmentPort = new GetAssignmentAdapter();
private final ExecutionFinishedEventPort executionFinishedEventPort = new ExecutionFinishedEventAdapter();
Logger logger = Logger.getLogger(ExecutorBase.class.getName());
protected ExecutorBase(ExecutorType executorType) {
logger.info("Starting Executor");
this.status = ExecutorStatus.STARTING_UP;
this.executorType = executorType;
// TODO set this automaticly
this.executorURI = new ExecutorURI("http://localhost:8084");
// TODO do this in main
// Notify executor-pool about existence. If executor-pools response is successfull start with getting an assignment, else shut down executor.
if(!notifyExecutorPoolService.notifyExecutorPool(this.executorURI, this.executorType)) {
logger.log(Level.WARNING, "Executor could not connect to executor pool! Shuting down!");
System.exit(0);
} else {
logger.info("Executor conntected to executor pool");
this.status = ExecutorStatus.IDLING;
getAssignment();
}
}
/**
* Requests a new task from the task queue
* @return void
**/
public void getAssignment() {
Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getExecutorURI());
if (newTask != null) {
logger.info("Executor got a new task");
this.executeTask(newTask);
} else {
logger.info("Executor got no new task");
this.status = ExecutorStatus.IDLING;
}
}
/**
* Start the execution of a task
* @return void
**/
private void executeTask(Task task) {
logger.info("Starting execution");
this.status = ExecutorStatus.EXECUTING;
task.setOutputData(execution(task.getInputData()));
// TODO implement logic if execution was not successful
executionFinishedEventPort.publishExecutionFinishedEvent(
new ExecutionFinishedEvent(task.getTaskID(), task.getOutputData(), "SUCCESS"));
logger.info("Finish execution");
getAssignment();
}
/**
* Implementation of the actual execution method of an executor
* @return the execution result
**/
protected abstract String execution(String input);
}

View File

@ -1,22 +0,0 @@
package ch.unisg.executorbase.executor.domain;
public enum ExecutorType {
ADDITION, ROBOT;
/**
* Checks if the give executor type exists.
* @return Wheter the given executor type exists
**/
public static boolean contains(String test) {
for (ExecutorType x : ExecutorType.values()) {
if (x.name().equals(test)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,66 @@
package ch.unisg.executorbase;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.services.GetAssignmentService;
import ch.unisg.executorbase.services.NotifyExecutorPoolService;
import lombok.Getter;
@Component
public class Executor {
@Getter
ExecutorStatus executorStatus = ExecutorStatus.STARTING_UP;
@Getter
@Value("${executor.type}")
String executorType;
@Getter
@Value("${executor.uri}")
ExecutorURI executorUri;
@Autowired
NotifyExecutorPoolService notifyExecutorPoolService;
@Autowired
GetAssignmentService getAssignmentService;
private Logger logger = Logger.getLogger(Executor.class.getName());
public Executor() {
executorStatus = ExecutorStatus.IDLING;
}
public void init() {
if(!notifyExecutorPoolService.executorStarted(this.executorUri, this.executorType)) {
logger.log(Level.WARNING, "ExecutorBase | Executor could not connect to executor pool! Shuting down!");
System.exit(0);
} else {
logger.info("ExecutorBase | Executor conntected to executor pool");
this.setIdling();
getAssignmentService.getAssignment();
}
}
// @PreDestroy
// public void preDestroy() {
// System.out.println("TEST");
// notifyExecutorPoolService.executorStopped(this.executorUri);
// }
public void setIdling() {
this.executorStatus = ExecutorStatus.IDLING;
}
public void setExecuting() {
this.executorStatus = ExecutorStatus.EXECUTING;
}
}

View File

@ -0,0 +1,22 @@
package ch.unisg.executorbase;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import ch.unisg.executorbase.services.NotifyExecutorPoolService;
public class ExecutorBase {
@Autowired
Executor executor;
@Autowired
NotifyExecutorPoolService notifyExecutorPoolService;
@PostConstruct
private void initialiseRoster(){
executor.init();
}
}

View File

@ -1,4 +1,4 @@
package ch.unisg.executorbase.executor.domain; package ch.unisg.executorbase;
public enum ExecutorStatus { public enum ExecutorStatus {
STARTING_UP, // Executor is starting STARTING_UP, // Executor is starting

View File

@ -1,4 +1,4 @@
package ch.unisg.executorbase.executor.domain; package ch.unisg.executorbase;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -6,7 +6,7 @@ import lombok.Setter;
public class Task { public class Task {
@Getter @Getter
private String taskID; private final String taskID;
@Getter @Getter
@Setter @Setter
@ -15,11 +15,16 @@ public class Task {
// TODO maybe create a value object for inputData so we can make sure it is in the right // TODO maybe create a value object for inputData so we can make sure it is in the right
// format. // format.
@Getter @Getter
private String inputData; private final String inputData;
public Task(String taskID, String inputData) { public Task(String taskID, String inputData) {
this.taskID = taskID; this.taskID = taskID;
this.inputData = inputData; this.inputData = inputData;
} }
public Task(String taskID) {
this.taskID = taskID;
this.inputData = "";
}
} }

View File

@ -1,7 +1,9 @@
package ch.unisg.executorbase.executor.adapter.in.web; package ch.unisg.executorbase.controller;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -9,17 +11,13 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableCommand; import ch.unisg.executorbase.services.TaskAvailableService;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableUseCase;
import ch.unisg.executorbase.executor.domain.ExecutorType;
@RestController @RestController
public class TaskAvailableController { public class TaskAvailableController {
private final TaskAvailableUseCase taskAvailableUseCase;
public TaskAvailableController(TaskAvailableUseCase taskAvailableUseCase) { @Autowired
this.taskAvailableUseCase = taskAvailableUseCase; private TaskAvailableService taskAvailableService;
}
Logger logger = Logger.getLogger(TaskAvailableController.class.getName()); Logger logger = Logger.getLogger(TaskAvailableController.class.getName());
@ -27,16 +25,12 @@ public class TaskAvailableController {
* Controller for notification about new events. * Controller for notification about new events.
* @return 200 OK * @return 200 OK
**/ **/
@GetMapping(path = "/newtask/{taskType}", consumes = { "application/json" }) @GetMapping(path = "/newtask/{taskType}")
public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) {
logger.info("New " + taskType + " available"); logger.info("ExecutorBase | New " + taskType + " task available");
if (ExecutorType.contains(taskType.toUpperCase())) { CompletableFuture.runAsync(() -> taskAvailableService.newTaskAvailable(taskType.toUpperCase()));
TaskAvailableCommand command = new TaskAvailableCommand(
ExecutorType.valueOf(taskType.toUpperCase()));
taskAvailableUseCase.newTaskAvailable(command);
}
// Add the content type as a response header // Add the content type as a response header
HttpHeaders responseHeaders = new HttpHeaders(); HttpHeaders responseHeaders = new HttpHeaders();

View File

@ -0,0 +1,42 @@
package ch.unisg.executorbase.services;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import ch.unisg.executorbase.Executor;
import ch.unisg.executorbase.Task;
public abstract class ExecuteTaskServiceBase implements ExecuteTaskServiceInterface {
private final Logger logger = Logger.getLogger(ExecuteTaskServiceBase.class.getName());
@Autowired
private Executor executor;
@Autowired
private GetAssignmentService getAssignmentService;
@Autowired
private ExecutionFinishedService executionFinishedService;
/**
* Start the execution of a task
* @return void
**/
public void executeTask(Task task) {
logger.info("ExecutorBase | Starting execution");
executor.setExecuting();
task.setOutputData(execution(task.getInputData()));
// TODO implement logic if execution was not successful
executionFinishedService.publishExecutionFinishedEvent(task.getTaskID(), task.getOutputData(), "SUCCESS");
logger.info("ExecutorBase | Finish execution");
getAssignmentService.getAssignment();
}
}

View File

@ -0,0 +1,13 @@
package ch.unisg.executorbase.services;
import ch.unisg.executorbase.Task;
public interface ExecuteTaskServiceInterface {
void executeTask(Task task);
/**
* Implementation of the actual execution method of an executor
* @return the execution result
**/
String execution(String input);
}

View File

@ -1,4 +1,4 @@
package ch.unisg.executorbase.executor.adapter.out.web; package ch.unisg.executorbase.services;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -9,34 +9,34 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.json.JSONObject; import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.executor.application.port.out.ExecutionFinishedEventPort; @Component
import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedService {
public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { @Value("${roster.uri}")
private String rosterUri;
String server = System.getenv("ROSTER_URI") == null ? private final Logger logger = Logger.getLogger(ExecutionFinishedService.class.getName());
"http://localhost:8082" : System.getenv("ROSTER_URI");
Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName());
/** /**
* Publishes the execution finished event * Publishes the execution finished event
* @return void * @return void
**/ **/
@Override public void publishExecutionFinishedEvent(String taskID, String outputData, String status) {
public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) {
logger.log(Level.INFO, "ExecutorBase | Sending finish execution event....");
String body = new JSONObject() String body = new JSONObject()
.put("taskID", event.getTaskID()) .put("taskID", taskID)
.put("outputData", event.getOutputData()) .put("outputData", outputData)
.put("status", event.getStatus()) .put("status", status)
.toString(); .toString();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/task/completed")) .uri(URI.create(rosterUri+"/task/completed"))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)) .POST(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
@ -51,8 +51,7 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort
logger.log(Level.SEVERE, e.getLocalizedMessage(), e); logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
} }
logger.log(Level.INFO, "Finish execution event sent with result: {0}", event.getOutputData()); logger.log(Level.INFO, "ExecutorBase | Finish execution event sent with result: {0}", outputData);
} }
} }

View File

@ -0,0 +1,90 @@
package ch.unisg.executorbase.services;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.Executor;
import ch.unisg.executorbase.Task;
@Component
@Primary
public class GetAssignmentService {
@Value("${roster.uri}")
String rosterUri;
Logger logger = Logger.getLogger(GetAssignmentService.class.getName());
@Autowired
private Executor executor;
@Autowired
private ExecuteTaskServiceInterface executeTaskService;
public void getAssignment() {
Task newTask = requestAssignment();
if (newTask != null) {
logger.info("ExecutorBase | Executor got a new task");
executeTaskService.executeTask(newTask);
} else {
logger.info("ExecutorBase | Executor got no new task");
executor.setIdling();
}
}
/**
* Requests a new task assignment
* @return the assigned task
* @see Task
**/
private Task requestAssignment() {
String body = new JSONObject()
.put("executorType", executor.getExecutorType())
.put("executorURI", executor.getExecutorUri().getValue())
.toString();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(rosterUri + "/task/apply"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
try {
logger.info("ExecutorBase | Sending getAssignment request");
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != HttpStatus.OK.value()) {
logger.info("ExecutorBase | No task assigned");
return null;
}
logger.info("ExecutorBase | Task assigned");
JSONObject responseBody = new JSONObject(response.body());
if (!responseBody.get("inputData").equals(null)) {
return new Task(responseBody.getString("taskID"), responseBody.getString("inputData"));
}
return new Task(responseBody.getString("taskID"));
} catch (InterruptedException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
return null;
}
}

View File

@ -1,4 +1,4 @@
package ch.unisg.executorbase.executor.adapter.out.web; package ch.unisg.executorbase.services;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -15,24 +15,21 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.unisg.common.valueobject.ExecutorURI; import ch.unisg.common.valueobject.ExecutorURI;
import ch.unisg.executorbase.executor.application.port.out.NotifyExecutorPoolPort;
import ch.unisg.executorbase.executor.domain.ExecutorType;
@Component @Component
@Primary @Primary
public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { public class NotifyExecutorPoolService {
String server = System.getenv("EXECUTOR_POOL_URI") == null ? @Value("${executor.pool.uri}")
"http://localhost:8083" : System.getenv("EXECUTOR_POOL_URI"); String executorPoolUri;
Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); Logger logger = Logger.getLogger(NotifyExecutorPoolService.class.getName());
/** /**
* Notifies the executor-pool about the startup of this executor * Notifies the executor-pool about the startup of this executor
* @return if the notification was successful * @return if the notification was successful
**/ **/
@Override public boolean executorStarted(ExecutorURI executorURI, String executorType) {
public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) {
String body = new JSONObject() String body = new JSONObject()
.put("executorTaskType", executorType) .put("executorTaskType", executorType)
@ -41,7 +38,7 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort {
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/executor-pool/AddExecutor")) .uri(URI.create(executorPoolUri + "/executor-pool/executors"))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)) .POST(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
@ -61,4 +58,26 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort {
return false; return false;
} }
/**
* Notifies the executor-pool about the shutdown of this executor
**/
public static void executorStopped(String executorURI) {
System.out.println("TEST");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8083" + "/executor-pool/executors/" + executorURI))
// .uri(URI.create(executorPoolUri + "/executor-pool/executors/" + executorURI))
.header("Content-Type", "application/json")
.DELETE()
.build();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (InterruptedException e) {
// logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// Thread.currentThread().interrupt();
} catch (IOException e) {
// logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
}
} }

View File

@ -0,0 +1,28 @@
package ch.unisg.executorbase.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.Executor;
import ch.unisg.executorbase.ExecutorStatus;
import lombok.RequiredArgsConstructor;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Component
@Transactional
public class TaskAvailableService {
@Autowired
Executor executor;
@Autowired
GetAssignmentService getAssignmentService;
public void newTaskAvailable(String taskType) {
if (executor.getExecutorStatus() == ExecutorStatus.IDLING && executor.getExecutorType().equalsIgnoreCase(taskType)) {
getAssignmentService.getAssignment();
}
}
}

View File

@ -1,6 +1,4 @@
server.port=8081 roster.uri=http://localhost:8082
roster.url=http://127.0.0.1:8082
executor.pool.url=http://127.0.0.1:8083
spring.profiles.active=chaos-monkey spring.profiles.active=chaos-monkey
chaos.monkey.enabled=false chaos.monkey.enabled=false

View File

@ -17,6 +17,7 @@
<java.version>11</java.version> <java.version>11</java.version>
<sonar.organization>scs-asse-fs21-group1</sonar.organization> <sonar.organization>scs-asse-fs21-group1</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url> <sonar.host.url>https://sonarcloud.io</sonar.host.url>
<skipTests>true</skipTests>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
@ -67,6 +68,23 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId> <artifactId>spring-boot-test</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>21.3.0</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>21.3.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,40 @@
package ch.unisg.executorcomputation;
import java.util.logging.Logger;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.services.ExecuteTaskServiceBase;
@Component
@Primary
public class ExecuteTaskService extends ExecuteTaskServiceBase {
Logger executorLogger = Logger.getLogger(ExecuteTaskService.class.getName());
@Override
public String execution(String input) {
executorLogger.info("Executor | Starting execution with inputData: " + input);
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("JavaScript");
String result = "";
try {
result = engine.eval(input).toString();
} catch (ScriptException e1) {
// TODO some logic if execution fails
executorLogger.severe(e1.getMessage());
return result;
}
executorLogger.info("Executor | Finish execution");
return result;
}
}

View File

@ -1,26 +1,30 @@
package ch.unisg.executorcomputation; package ch.unisg.executorcomputation;
import java.util.concurrent.TimeUnit; import java.util.logging.Logger;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import ch.unisg.executorcomputation.executor.domain.Executor; import ch.unisg.executorbase.ExecutorBase;
import ch.unisg.executorbase.services.NotifyExecutorPoolService;
@SpringBootApplication @SpringBootApplication
public class ExecutorcomputationApplication { @ComponentScan({"ch.unisg.executorbase", "ch.unisg.executorcomputation"})
public class ExecutorcomputationApplication extends ExecutorBase {
static Logger logger = Logger.getLogger(ExecutorcomputationApplication.class.getName());
public static void main(String[] args) { public static void main(String[] args) {
/**
try { * This is not a nice solution but I didn't get the @PreDestroy hook to work... This is the
TimeUnit.SECONDS.sleep(40); * only solution which was working so I had to make the executorStopped function static
} catch (InterruptedException e) { * for now.
// TODO Auto-generated catch block */
e.printStackTrace(); Thread printingHook = new Thread(() -> NotifyExecutorPoolService.executorStopped("http://executor-computation:8085"));
} Runtime.getRuntime().addShutdownHook(printingHook);
SpringApplication.run(ExecutorcomputationApplication.class, args); SpringApplication.run(ExecutorcomputationApplication.class, args);
Executor.getExecutor();
} }
} }

View File

@ -1,35 +0,0 @@
package ch.unisg.executorcomputation.executor.adapter.in.web;
import java.util.concurrent.CompletableFuture;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableCommand;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableUseCase;
import ch.unisg.executorbase.executor.domain.ExecutorType;
@RestController
public class TaskAvailableController {
private final TaskAvailableUseCase taskAvailableUseCase;
public TaskAvailableController(TaskAvailableUseCase taskAvailableUseCase) {
this.taskAvailableUseCase = taskAvailableUseCase;
}
@GetMapping(path = "/newtask/{taskType}")
public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) {
if (ExecutorType.contains(taskType.toUpperCase())) {
TaskAvailableCommand command = new TaskAvailableCommand(
ExecutorType.valueOf(taskType.toUpperCase()));
CompletableFuture.runAsync(() -> taskAvailableUseCase.newTaskAvailable(command));
}
return new ResponseEntity<>("OK", new HttpHeaders(), HttpStatus.OK);
}
}

View File

@ -1,28 +0,0 @@
package ch.unisg.executorcomputation.executor.application.service;
import org.springframework.stereotype.Component;
import ch.unisg.executorcomputation.executor.domain.Executor;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableCommand;
import ch.unisg.executorbase.executor.application.port.in.TaskAvailableUseCase;
import ch.unisg.executorbase.executor.domain.ExecutorStatus;
import lombok.RequiredArgsConstructor;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Component
@Transactional
public class TaskAvailableService implements TaskAvailableUseCase {
@Override
public void newTaskAvailable(TaskAvailableCommand command) {
Executor executor = Executor.getExecutor();
if (executor.getExecutorType() == command.getTaskType() &&
executor.getStatus() == ExecutorStatus.IDLING) {
executor.getAssignment();
}
}
}

View File

@ -1,63 +0,0 @@
package ch.unisg.executorcomputation.executor.domain;
import java.util.concurrent.TimeUnit;
import ch.unisg.executorbase.executor.domain.ExecutorBase;
import ch.unisg.executorbase.executor.domain.ExecutorType;
public class Executor extends ExecutorBase {
private static final Executor executor = new Executor(ExecutorType.ADDITION);
public static Executor getExecutor() {
return executor;
}
private Executor(ExecutorType executorType) {
super(executorType);
}
@Override
protected
String execution(String inputData) {
String operator = "";
if (inputData.contains("+")) {
operator = "+";
} else if (inputData.contains("-")) {
operator = "-";
} else if (inputData.contains("*")) {
operator = "*";
} else {
return "invalid data";
}
double result = Double.NaN;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (operator.equalsIgnoreCase("+")) {
String[] parts = inputData.split("\\+");
double a = Double.parseDouble(parts[0]);
double b = Double.parseDouble(parts[1]);
result = a + b;
} else if (operator.equalsIgnoreCase("*")) {
String[] parts = inputData.split("\\*");
double a = Double.parseDouble(parts[0]);
double b = Double.parseDouble(parts[1]);
result = a * b;
} else if (operator.equalsIgnoreCase("-")) {
String[] parts = inputData.split("-");
double a = Double.parseDouble(parts[0]);
double b = Double.parseDouble(parts[1]);
result = a - b;
}
return Double.toString(result);
}
}

View File

@ -1,5 +1,10 @@
server.port=8085 server.port=8085
executor.type=COMPUTATION
executor.uri=http://localhost:8085
roster.uri=http://localhost:8082
executor.pool.uri=http://localhost:8083
spring.profiles.active=chaos-monkey spring.profiles.active=chaos-monkey
chaos.monkey.enabled=false chaos.monkey.enabled=false
management.endpoint.chaosmonkey.enabled=true management.endpoint.chaosmonkey.enabled=true

33
executor-humidity/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@ -0,0 +1,3 @@
# Californium3 CoAP Properties file
# Wed Dec 15 22:23:03 CET 2021
#

310
executor-humidity/mvnw vendored Executable file
View File

@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
executor-humidity/mvnw.cmd vendored Normal file
View File

@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

81
executor-humidity/pom.xml Normal file
View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ch.unisg</groupId>
<artifactId>executor-humidity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>executor-humidity</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.unisg</groupId>
<artifactId>executor-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
<version>3.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.Interactions-HSG</groupId>
<artifactId>wot-td-java</artifactId>
<version>0.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,27 @@
package ch.unisg.executorhumidity;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.executorbase.services.ExecuteTaskServiceBase;
@Component
@Primary
public class ExecuteTaskService extends ExecuteTaskServiceBase {
@Autowired
GetHumidityService getHumidityService;
private Logger executorLogger = Logger.getLogger(ExecuteTaskService.class.getName());
@Override
public String execution(String input) {
executorLogger.info("Executor | Starting execution with inputData: " + input);
return getHumidityService.getHumidity();
}
}

View File

@ -0,0 +1,25 @@
package ch.unisg.executorhumidity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import ch.unisg.executorbase.ExecutorBase;
import ch.unisg.executorbase.services.NotifyExecutorPoolService;
@SpringBootApplication
@ComponentScan({"ch.unisg.executorbase", "ch.unisg.executorhumidity"})
public class ExecutorhumidityApplication extends ExecutorBase {
public static void main(String[] args) {
/**
* This is not a nice solution but I didn't get the @PreDestroy hook to work... This is the
* only solution which was working so I had to make the executorStopped function static
* for now.
*/
Thread printingHook = new Thread(() -> NotifyExecutorPoolService.executorStopped("http://executor-humidity:8087"));
Runtime.getRuntime().addShutdownHook(printingHook);
SpringApplication.run(ExecutorhumidityApplication.class, args);
}
}

View File

@ -0,0 +1,105 @@
package ch.unisg.executorhumidity;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.Optional;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import ch.unisg.ics.interactions.wot.td.ThingDescription;
import ch.unisg.ics.interactions.wot.td.affordances.Form;
import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance;
import ch.unisg.ics.interactions.wot.td.clients.TDCoapRequest;
import ch.unisg.ics.interactions.wot.td.clients.TDCoapResponse;
import ch.unisg.ics.interactions.wot.td.io.TDGraphReader;
import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema;
import ch.unisg.ics.interactions.wot.td.vocabularies.TD;
@Component
public class GetHumidityService {
@Value("${search.engine.uri}")
String endpoint;
public String getHumidity() {
String input = "@prefix dct: <http://purl.org/dc/terms/> . select ?title where { ?title dct:title 'Mirogate' }";
var httpRequest = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/sparql-query")
.POST(HttpRequest.BodyPublishers.ofString(input))
.build();
var client = HttpClient.newHttpClient();
try {
String description = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()).body();
String mirogateUri = null;
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(description)));
doc.getDocumentElement().normalize();
NodeList results = doc.getElementsByTagName("uri");
for (int temp = 0; temp < results.getLength(); temp += 1) {
Node nNode = results.item(temp);
if (nNode.getTextContent().contains("mirogate")) {
mirogateUri = nNode.getTextContent();
}
}
if (mirogateUri == null) {
// TODO implement logic if execution failed
return "";
}
// Parse a TD from a string
ThingDescription td = TDGraphReader.readFromURL(ThingDescription.TDFormat.RDF_TURTLE, mirogateUri);
Optional<PropertyAffordance> humidity = td.getPropertyByName("humidity");
if (humidity.isPresent()) {
Optional<Form> form = humidity.get().getFirstFormForOperationType(TD.readProperty);
if (form.isPresent()) {
TDCoapRequest request = new TDCoapRequest(form.get(), TD.readProperty);
TDCoapResponse response = request.execute();
Map<String, Object> payload = response.getPayloadAsObject((ObjectSchema) humidity.get().getDataSchema());
Object result = payload.get("https://interactions.ics.unisg.ch/mirogate#HumidityValue");
return result.toString();
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ParserConfigurationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SAXException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,20 @@
server.port=8087
search.engine.uri=https://api.interactions.ics.unisg.ch/search/searchEngine
executor.type=HUMIDITY
executor.uri=http://localhost:8087
roster.uri=http://localhost:8082
executor.pool.uri=http://localhost:8083
spring.profiles.active=chaos-monkey
chaos.monkey.enabled=false
management.endpoint.chaosmonkey.enabled=true
management.endpoint.chaosmonkeyjmx.enabled=true
# include specific endpoints
management.endpoints.web.exposure.include=health,info,chaosmonkey
chaos.monkey.watcher.controller=true
chaos.monkey.watcher.restController=true
chaos.monkey.watcher.service=true
chaos.monkey.watcher.repository=true
chaos.monkey.watcher.component=true

View File

@ -9,7 +9,7 @@ import java.util.Set;
public class SelfValidating<T> { public class SelfValidating<T> {
private Validator validator; private final Validator validator;
public SelfValidating() { public SelfValidating() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

View File

@ -1,7 +1,5 @@
package ch.unisg.executorpool; package ch.unisg.executorpool;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -21,14 +19,6 @@ public class ExecutorPoolApplication {
private LoadExecutorPort loadExecutorPort; private LoadExecutorPort loadExecutorPort;
public static void main(String[] args) { public static void main(String[] args) {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SpringApplication.run(ExecutorPoolApplication.class, args); SpringApplication.run(ExecutorPoolApplication.class, args);
} }

Some files were not shown because too many files have changed in this diff Show More