From 3f10ab47fbc8f48e7dc56ce9b8fd2eb6d1323a9c Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 5 Oct 2021 14:56:15 +0200 Subject: [PATCH 01/94] fixed some errors --- app/src/main/java/com/app/hello/Application.java | 2 +- app/src/main/java/com/app/hello/HelloController.java | 2 +- app/src/test/java/com/app/hello/DummyTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/app/hello/Application.java b/app/src/main/java/com/app/hello/Application.java index 8f330a7..4c5e45f 100755 --- a/app/src/main/java/com/app/hello/Application.java +++ b/app/src/main/java/com/app/hello/Application.java @@ -1,4 +1,4 @@ -package com.dockerforjavadevelopers.hello; +package com.app.hello; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; diff --git a/app/src/main/java/com/app/hello/HelloController.java b/app/src/main/java/com/app/hello/HelloController.java index 8d69328..d8f810b 100755 --- a/app/src/main/java/com/app/hello/HelloController.java +++ b/app/src/main/java/com/app/hello/HelloController.java @@ -1,4 +1,4 @@ -package com.dockerforjavadevelopers.hello; +package com.app.hello; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/app/src/test/java/com/app/hello/DummyTest.java b/app/src/test/java/com/app/hello/DummyTest.java index 7d25f73..7447934 100755 --- a/app/src/test/java/com/app/hello/DummyTest.java +++ b/app/src/test/java/com/app/hello/DummyTest.java @@ -1,4 +1,4 @@ -package com.dockerforjavadevelopers.hello; +package com.app.hello; import static org.junit.Assert.*; -- 2.45.1 From 9dabdeb36eb2c123b70990b7b3f0618cfe22328d Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 Oct 2021 10:23:17 +0200 Subject: [PATCH 02/94] init --- .github/workflows/build-and-deploy.yml | 109 ++- .gitignore | 4 + assignment/.gitignore | 33 + .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ++++ assignment/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + assignment/mvnw | 310 +++++++++ assignment/mvnw.cmd | 182 +++++ assignment/pom.xml | 60 ++ .../assignment/AssignmentApplication.java | 13 + .../ch/unisg/assignment/TestController.java | 12 + .../src/main/resources/application.properties | 1 + .../AssignmentApplicationTests.java | 13 + diagram_1.bpmn | 653 ++++++++++++++++++ executor-pool/.gitignore | 33 + .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ++++ executor-pool/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + executor-pool/mvnw | 310 +++++++++ executor-pool/mvnw.cmd | 182 +++++ executor-pool/pom.xml | 65 ++ .../executorpool/ExecutorPoolApplication.java | 13 + .../ch/unisg/executorpool/TestController.java | 12 + .../src/main/resources/application.properties | 1 + .../ExecutorPoolApplicationTests.java | 13 + executor1/.gitignore | 33 + .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ++++ executor1/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + executor1/mvnw | 310 +++++++++ executor1/mvnw.cmd | 182 +++++ executor1/pom.xml | 60 ++ .../unisg/executor1/Executor1Application.java | 13 + .../ch/unisg/executor1/TestController.java | 12 + .../src/main/resources/application.properties | 1 + .../executor1/Executor1ApplicationTests.java | 13 + executor2/.gitignore | 33 + .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ++++ executor2/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + executor2/mvnw | 310 +++++++++ executor2/mvnw.cmd | 182 +++++ executor2/pom.xml | 60 ++ .../unisg/executor2/Executor2Application.java | 13 + .../ch/unisg/executor2/TestController.java | 12 + .../src/main/resources/application.properties | 1 + .../executor2/Executor2ApplicationTests.java | 13 + tapas-tasks/pom.xml | 7 + 48 files changed, 3694 insertions(+), 56 deletions(-) create mode 100644 assignment/.gitignore create mode 100644 assignment/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 assignment/.mvn/wrapper/maven-wrapper.jar create mode 100644 assignment/.mvn/wrapper/maven-wrapper.properties create mode 100755 assignment/mvnw create mode 100644 assignment/mvnw.cmd create mode 100644 assignment/pom.xml create mode 100644 assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/TestController.java create mode 100644 assignment/src/main/resources/application.properties create mode 100644 assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java create mode 100644 diagram_1.bpmn create mode 100644 executor-pool/.gitignore create mode 100644 executor-pool/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 executor-pool/.mvn/wrapper/maven-wrapper.jar create mode 100644 executor-pool/.mvn/wrapper/maven-wrapper.properties create mode 100755 executor-pool/mvnw create mode 100644 executor-pool/mvnw.cmd create mode 100644 executor-pool/pom.xml create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/ExecutorPoolApplication.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/TestController.java create mode 100644 executor-pool/src/main/resources/application.properties create mode 100644 executor-pool/src/test/java/ch/unisg/executorpool/ExecutorPoolApplicationTests.java create mode 100644 executor1/.gitignore create mode 100644 executor1/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 executor1/.mvn/wrapper/maven-wrapper.jar create mode 100644 executor1/.mvn/wrapper/maven-wrapper.properties create mode 100755 executor1/mvnw create mode 100644 executor1/mvnw.cmd create mode 100644 executor1/pom.xml create mode 100644 executor1/src/main/java/ch/unisg/executor1/Executor1Application.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/TestController.java create mode 100644 executor1/src/main/resources/application.properties create mode 100644 executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java create mode 100644 executor2/.gitignore create mode 100644 executor2/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 executor2/.mvn/wrapper/maven-wrapper.jar create mode 100644 executor2/.mvn/wrapper/maven-wrapper.properties create mode 100755 executor2/mvnw create mode 100644 executor2/mvnw.cmd create mode 100644 executor2/pom.xml create mode 100644 executor2/src/main/java/ch/unisg/executor2/Executor2Application.java create mode 100644 executor2/src/main/java/ch/unisg/executor2/TestController.java create mode 100644 executor2/src/main/resources/application.properties create mode 100644 executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 619804e..43bb471 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -8,6 +8,8 @@ on: branches: - main + workflow_dispatch: + jobs: build: runs-on: ubuntu-latest @@ -15,67 +17,62 @@ jobs: contents: read steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: "11" + distribution: "adopt" - - - run: mkdir ./target + - run: mkdir ./target - - name: Build with Maven - 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 + - name: Build with Maven + 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 - - name: Build with Maven - run: mvn -f app/pom.xml --batch-mode --update-snapshots verify - - run: cp ./app/target/app-0.1.0.jar ./target + - name: Build with Maven + run: mvn -f app/pom.xml --batch-mode --update-snapshots verify + - run: cp ./app/target/app-0.1.0.jar ./target + + - run: cp ./.deployment/docker-compose.yml ./target + - name: Archive artifacts + uses: actions/upload-artifact@v1 + with: + name: app + path: ./target/ - - run: cp docker-compose.yml ./target - - name: Archive artifacts - uses: actions/upload-artifact@v1 - with: - name: app - path: ./target/ - - deploy: runs-on: ubuntu-latest - needs: [build, ] + needs: [build] steps: - - name: Download app artifacts - uses: actions/download-artifact@v1 - with: - name: app - - name: Copy host via scp - uses: appleboy/scp-action@master - env: - HOST: ${{ secrets.SSH_HOST }} - USERNAME: ${{ secrets.SSH_USER }} - PORT: 22 - KEY: ${{ secrets.SSH_PRIVATE_KEY }} - with: - source: "app/*" - target: "/home/${{ secrets.SSH_USER }}/" - strip_components: 1 - rm: false - overwrite: true + - name: Download app artifacts + uses: actions/download-artifact@v1 + with: + name: app + - name: Copy host via scp + uses: appleboy/scp-action@master + env: + HOST: ${{ secrets.SSH_HOST }} + USERNAME: ${{ secrets.SSH_USER }} + PORT: 22 + KEY: ${{ secrets.SSH_PRIVATE_KEY }} + with: + source: "app/*" + target: "/home/${{ secrets.SSH_USER }}/" + strip_components: 1 + rm: false + overwrite: true - - name: Executing remote command - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.SSH_HOST }} - USERNAME: ${{ secrets.SSH_USER }} - PORT: 22 - KEY: ${{ secrets.SSH_PRIVATE_KEY }} - script: | - cd /home/${{ secrets.SSH_USER }}/ - touch acme.json - sudo chmod 0600 acme.json - sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env - sudo docker-compose up -d - - - + - name: Executing remote command + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SSH_HOST }} + USERNAME: ${{ secrets.SSH_USER }} + PORT: 22 + KEY: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /home/${{ secrets.SSH_USER }}/ + touch acme.json + sudo chmod 0600 acme.json + sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env + sudo docker-compose up -d diff --git a/.gitignore b/.gitignore index 549e00a..e01b495 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ build/ ### VS Code ### .vscode/ + +### Mac ### +.DS_Store + diff --git a/assignment/.gitignore b/assignment/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/assignment/.gitignore @@ -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/ diff --git a/assignment/.mvn/wrapper/MavenWrapperDownloader.java b/assignment/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/assignment/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/assignment/.mvn/wrapper/maven-wrapper.jar b/assignment/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/assignment/.mvn/wrapper/maven-wrapper.properties b/assignment/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/assignment/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/assignment/mvnw b/assignment/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/assignment/mvnw @@ -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 "$@" diff --git a/assignment/mvnw.cmd b/assignment/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/assignment/mvnw.cmd @@ -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% diff --git a/assignment/pom.xml b/assignment/pom.xml new file mode 100644 index 0000000..e58d10a --- /dev/null +++ b/assignment/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + ch.unisg + assignment + 0.0.1-SNAPSHOT + assignment + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java new file mode 100644 index 0000000..30d7782 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java @@ -0,0 +1,13 @@ +package ch.unisg.assignment; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AssignmentApplication { + + public static void main(String[] args) { + SpringApplication.run(AssignmentApplication.class, args); + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/TestController.java b/assignment/src/main/java/ch/unisg/assignment/TestController.java new file mode 100644 index 0000000..ac8e4f9 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/TestController.java @@ -0,0 +1,12 @@ +package ch.unisg.assignment; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @RequestMapping("/") + public String index() { + return "Hello World! Assignment"; + } +} diff --git a/assignment/src/main/resources/application.properties b/assignment/src/main/resources/application.properties new file mode 100644 index 0000000..4d360de --- /dev/null +++ b/assignment/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8081 diff --git a/assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java b/assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java new file mode 100644 index 0000000..9da24b5 --- /dev/null +++ b/assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.assignment; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AssignmentApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/diagram_1.bpmn b/diagram_1.bpmn new file mode 100644 index 0000000..313a860 --- /dev/null +++ b/diagram_1.bpmn @@ -0,0 +1,653 @@ + + + + + + + + + + + + + + Gateway_1vd3as7 + Activity_1jd11bs + Gateway_1e4ckdq + Activity_1dl4fvt + Gateway_1e3rabp + Event_1lm6x5y + Event_1ysgenb + Event_00rvb9o + Activity_0u3tts0 + Activity_0vs4eam + Activity_0mwpp9o + Event_0v7hm2z + Activity_0paecdb + Activity_0srcl99 + Activity_0assw9c + + + Activity_0jai885 + StartEvent_1 + + + + Flow_1hc51tx + Flow_1sajzlx + Flow_1ijfkpz + + + Flow_1sajzlx + Flow_0cpd5ad + + + Flow_0cpd5ad + Flow_0kwvrmc + Flow_1w3uh2m + + + Flow_0kwvrmc + Flow_0vpcut0 + + + Flow_0vpcut0 + Flow_179e0hl + Flow_12nh0g5 + + + Flow_179e0hl + + + Flow_0366zqm + + + Flow_1n8jm89 + + + Flow_1w3uh2m + Flow_1n8jm89 + + + Flow_19dbo28 + Flow_1rwgf2n + + + Flow_19dbo28 + + + Flow_1ijfkpz + Flow_1gvdy5x + + + Flow_1gvdy5x + Flow_1yxp4e8 + + + Flow_1yxp4e8 + + + Flow_1rwgf2n + Flow_1hc51tx + + + + Flow_12nh0g5 + Flow_0366zqm + + + + + + + + + + + + + + + + + + + + Flow_119ldsr + + + Flow_1mm9swr + + + Flow_119ldsr + Flow_1mm9swr + + + + + + + + Event_0v73rhy + Activity_0n8uuvk + + + Activity_06xjrrk + + + Activity_0uxkytf + Event_01nh9j2 + Activity_1qgjnyh + + + + Flow_17d0j42 + + + Flow_17d0j42 + Flow_0opy5tp + + + Flow_0opy5tp + Flow_0sudw7l + + + Flow_0sudw7l + Flow_02uzxx3 + + + Flow_0rpv16j + + + Flow_02uzxx3 + Flow_0rpv16j + + + + + + + + + + + Event_1oz3tr5 + Activity_0xk9kck + + + Gateway_079742h + Activity_0umieiz + Event_062x6a9 + Activity_0b6bh6v + Event_1achffx + Activity_1mme68o + + + + Flow_0od6iot + + + Flow_0od6iot + Flow_0k07ofo + + + Flow_0k07ofo + Flow_1fwmda3 + Flow_050yku1 + + + Flow_1fwmda3 + Flow_15lkkxa + + + Flow_15lkkxa + + + Flow_050yku1 + Flow_1lgcq8d + + + Flow_0oqra8s + + + Flow_1lgcq8d + Flow_0oqra8s + + + + + + + + + + + + + Event_1b2jt5y + Activity_1ikvmpl + Event_0y66dbe + Activity_11lqrhg + Activity_1oownmu + + + Activity_1ohwol1 + Event_0bbqq3x + + + Activity_1e9hb9h + Event_0l7ljcr + + + + Flow_046hsk4 + + + Flow_046hsk4 + Flow_0zoqmub + + + Flow_0nyxv8e + + + Flow_0zoqmub + Flow_0ip5x3i + Flow_1bqiz65 + Flow_142xsjm + + + Flow_1bqiz65 + Flow_0znvsoe + + + Flow_142xsjm + Flow_0ue2i8v + + + Flow_0ue2i8v + + + Flow_0znvsoe + + + Flow_0ip5x3i + Flow_0nyxv8e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/executor-pool/.gitignore b/executor-pool/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/executor-pool/.gitignore @@ -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/ diff --git a/executor-pool/.mvn/wrapper/MavenWrapperDownloader.java b/executor-pool/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/executor-pool/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/executor-pool/.mvn/wrapper/maven-wrapper.jar b/executor-pool/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/executor-pool/.mvn/wrapper/maven-wrapper.properties b/executor-pool/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/executor-pool/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/executor-pool/mvnw b/executor-pool/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/executor-pool/mvnw @@ -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 "$@" diff --git a/executor-pool/mvnw.cmd b/executor-pool/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/executor-pool/mvnw.cmd @@ -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% diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml new file mode 100644 index 0000000..d96a76d --- /dev/null +++ b/executor-pool/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + ch.unisg + executor-pool + 0.0.1-SNAPSHOT + executor-pool + Executor Pool + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.json + json + 20210307 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/ExecutorPoolApplication.java b/executor-pool/src/main/java/ch/unisg/executorpool/ExecutorPoolApplication.java new file mode 100644 index 0000000..5cd0108 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/ExecutorPoolApplication.java @@ -0,0 +1,13 @@ +package ch.unisg.executorpool; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ExecutorPoolApplication { + + public static void main(String[] args) { + SpringApplication.run(ExecutorPoolApplication.class, args); + } + +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java new file mode 100644 index 0000000..ca29e09 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java @@ -0,0 +1,12 @@ +package ch.unisg.executorpool; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @RequestMapping("/") + public String index() { + return "Hello World! Executor Pool"; + } +} diff --git a/executor-pool/src/main/resources/application.properties b/executor-pool/src/main/resources/application.properties new file mode 100644 index 0000000..4d360de --- /dev/null +++ b/executor-pool/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8081 diff --git a/executor-pool/src/test/java/ch/unisg/executorpool/ExecutorPoolApplicationTests.java b/executor-pool/src/test/java/ch/unisg/executorpool/ExecutorPoolApplicationTests.java new file mode 100644 index 0000000..77e1032 --- /dev/null +++ b/executor-pool/src/test/java/ch/unisg/executorpool/ExecutorPoolApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.executorpool; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ExecutorPoolApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/executor1/.gitignore b/executor1/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/executor1/.gitignore @@ -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/ diff --git a/executor1/.mvn/wrapper/MavenWrapperDownloader.java b/executor1/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/executor1/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/executor1/.mvn/wrapper/maven-wrapper.jar b/executor1/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/executor1/.mvn/wrapper/maven-wrapper.properties b/executor1/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/executor1/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/executor1/mvnw b/executor1/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/executor1/mvnw @@ -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 "$@" diff --git a/executor1/mvnw.cmd b/executor1/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/executor1/mvnw.cmd @@ -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% diff --git a/executor1/pom.xml b/executor1/pom.xml new file mode 100644 index 0000000..cdadeff --- /dev/null +++ b/executor1/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + ch.unisg + executor1 + 0.0.1-SNAPSHOT + executor1 + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java new file mode 100644 index 0000000..4eed560 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java @@ -0,0 +1,13 @@ +package ch.unisg.executor1; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Executor1Application { + + public static void main(String[] args) { + SpringApplication.run(Executor1Application.class, args); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/TestController.java b/executor1/src/main/java/ch/unisg/executor1/TestController.java new file mode 100644 index 0000000..a2f32b4 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/TestController.java @@ -0,0 +1,12 @@ +package ch.unisg.executor1; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @RequestMapping("/") + public String index() { + return "Hello World! Executor1"; + } +} diff --git a/executor1/src/main/resources/application.properties b/executor1/src/main/resources/application.properties new file mode 100644 index 0000000..4d360de --- /dev/null +++ b/executor1/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8081 diff --git a/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java b/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java new file mode 100644 index 0000000..889c9cd --- /dev/null +++ b/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.executor1; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Executor1ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/executor2/.gitignore b/executor2/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/executor2/.gitignore @@ -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/ diff --git a/executor2/.mvn/wrapper/MavenWrapperDownloader.java b/executor2/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/executor2/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/executor2/.mvn/wrapper/maven-wrapper.jar b/executor2/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/executor2/.mvn/wrapper/maven-wrapper.properties b/executor2/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/executor2/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/executor2/mvnw b/executor2/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/executor2/mvnw @@ -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 "$@" diff --git a/executor2/mvnw.cmd b/executor2/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/executor2/mvnw.cmd @@ -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% diff --git a/executor2/pom.xml b/executor2/pom.xml new file mode 100644 index 0000000..d3e7318 --- /dev/null +++ b/executor2/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + ch.unisg + executor2 + 0.0.1-SNAPSHOT + executor2 + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java b/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java new file mode 100644 index 0000000..d31e277 --- /dev/null +++ b/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java @@ -0,0 +1,13 @@ +package ch.unisg.executor2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Executor2Application { + + public static void main(String[] args) { + SpringApplication.run(Executor2Application.class, args); + } + +} diff --git a/executor2/src/main/java/ch/unisg/executor2/TestController.java b/executor2/src/main/java/ch/unisg/executor2/TestController.java new file mode 100644 index 0000000..c98bb02 --- /dev/null +++ b/executor2/src/main/java/ch/unisg/executor2/TestController.java @@ -0,0 +1,12 @@ +package ch.unisg.executor2; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @RequestMapping("/") + public String index() { + return "Hello World! Executor2"; + } +} diff --git a/executor2/src/main/resources/application.properties b/executor2/src/main/resources/application.properties new file mode 100644 index 0000000..4d360de --- /dev/null +++ b/executor2/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8081 diff --git a/executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java b/executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java new file mode 100644 index 0000000..5724a1c --- /dev/null +++ b/executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.executor2; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Executor2ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index a5b6587..503984f 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -60,6 +60,13 @@ 1.13 + + org.springframework.boot + spring-boot-devtools + runtime + true + + -- 2.45.1 From c6aebe4b9f5bdf766b3adc47bc3cf409934149dd Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 Oct 2021 10:23:57 +0200 Subject: [PATCH 03/94] added docker stuff for development --- .../docker-compose.yml | 0 README.md | 145 ++++++++++++------ assignment/Dockerfile | 18 +++ docker-compose.yaml | 62 ++++++++ executor-pool/Dockerfile | 18 +++ executor1/Dockerfile | 18 +++ executor2/Dockerfile | 18 +++ tapas-tasks/Dockerfile | 18 +++ 8 files changed, 251 insertions(+), 46 deletions(-) rename docker-compose.yml => .deployment/docker-compose.yml (100%) create mode 100644 assignment/Dockerfile create mode 100644 docker-compose.yaml create mode 100644 executor-pool/Dockerfile create mode 100644 executor1/Dockerfile create mode 100644 executor2/Dockerfile create mode 100644 tapas-tasks/Dockerfile diff --git a/docker-compose.yml b/.deployment/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to .deployment/docker-compose.yml diff --git a/README.md b/README.md index bcc7355..57106c5 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,113 @@ # TAPAS + This is the main GitHub project for your implementation of the TAPAS application. +## 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 + This project is structured as follows: -* [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/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) + +- [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/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 ### 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` +- 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 +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..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(`.${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 + +- 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..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(`.${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 + +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) + +- 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: + +- 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). + - 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..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`. @@ -64,19 +115,21 @@ For the server IP address (see below), you should use dashes instead of dots, e. ## VM Configurations Specs (we can upgrade if needed): -* 1 CPU -* 2 GB RAM -* 20 GB HD -* Ubuntu 20.04 -| Name | Server IP | -|-------|-----------| -|SCS-ASSE-VM-Group1|86.119.35.40| -|SCS-ASSE-VM-Group2|86.119.35.213| -|SCS-ASSE-VM-Group3|86.119.34.242| -|SCS-ASSE-VM-Group4|86.119.35.199| -|SCS-ASSE-VM-Group5|86.119.35.72| +- 1 CPU +- 2 GB RAM +- 20 GB HD +- Ubuntu 20.04 + +| Name | Server IP | +| ------------------ | ------------- | +| SCS-ASSE-VM-Group1 | 86.119.35.40 | +| SCS-ASSE-VM-Group2 | 86.119.35.213 | +| SCS-ASSE-VM-Group3 | 86.119.34.242 | +| SCS-ASSE-VM-Group4 | 86.119.35.199 | +| 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. \ No newline at end of file + +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. diff --git a/assignment/Dockerfile b/assignment/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/assignment/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..76b8af1 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,62 @@ +version: "3.6" +services: + tapas-tasks: + container_name: tapas-tasks + build: + context: "./tapas-tasks" + dockerfile: "Dockerfile" + target: development + ports: + - "8081:8081" + - "5005:5005" + volumes: + - ./tapas-tasks/src:/opt/app/src + - ./tapas-tasks/target:/opt/app/target + assignment: + container_name: assignment + build: + context: "./assignment" + dockerfile: "Dockerfile" + target: development + ports: + - "8082:8081" + - "5006:5005" + volumes: + - ./assignment/src:/opt/app/src + - ./assignment/target:/opt/app/target + executor-pool: + container_name: executor-pool + build: + context: "./executor-pool" + dockerfile: "Dockerfile" + target: development + ports: + - "8083:8081" + - "5007:5005" + volumes: + - ./executor-pool/src:/opt/app/src + - ./executor-pool/target:/opt/app/target + executor1: + container_name: executor1 + build: + context: "./executor1" + dockerfile: "Dockerfile" + target: development + ports: + - "8084:8081" + - "5008:5005" + volumes: + - ./executor1/src:/opt/app/src + - ./executor1/target:/opt/app/target + executor2: + container_name: executor2 + build: + context: "./executor2" + dockerfile: "Dockerfile" + target: development + ports: + - "8085:8081" + - "5009:5005" + volumes: + - ./executor2/src:/opt/app/src + - ./executor2/target:/opt/app/target diff --git a/executor-pool/Dockerfile b/executor-pool/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/executor-pool/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] diff --git a/executor1/Dockerfile b/executor1/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/executor1/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] diff --git a/executor2/Dockerfile b/executor2/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/executor2/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] diff --git a/tapas-tasks/Dockerfile b/tapas-tasks/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/tapas-tasks/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] -- 2.45.1 From 5d37aea3d7862f0fb2411ee2e56b98f14b92f4af Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 Oct 2021 18:11:18 +0200 Subject: [PATCH 04/94] added code quality scanns --- .github/workflows/ci.assignment.yml | 41 +++++++++++++++++++ .github/workflows/ci.tapas-tasks.yml | 41 +++++++++++++++++++ assignment/pom.xml | 2 + .../assignment/AssignmentApplication.java | 1 + executor-pool/pom.xml | 2 + executor1/pom.xml | 2 + executor2/pom.xml | 2 + tapas-tasks/pom.xml | 2 + 8 files changed, 93 insertions(+) create mode 100644 .github/workflows/ci.assignment.yml create mode 100644 .github/workflows/ci.tapas-tasks.yml diff --git a/.github/workflows/ci.assignment.yml b/.github/workflows/ci.assignment.yml new file mode 100644 index 0000000..32b8c58 --- /dev/null +++ b/.github/workflows/ci.assignment.yml @@ -0,0 +1,41 @@ +name: Build +on: + push: + branches: [main, dev] + paths: + - "tapas-tasks/**" + pull_request: + branches: [main, dev] + paths: + - "tapas-tasks/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f assignment/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=SCS-ASSE-FS21-Group1_tapas diff --git a/.github/workflows/ci.tapas-tasks.yml b/.github/workflows/ci.tapas-tasks.yml new file mode 100644 index 0000000..c3970a0 --- /dev/null +++ b/.github/workflows/ci.tapas-tasks.yml @@ -0,0 +1,41 @@ +name: Build +on: + push: + branches: [main, dev] + paths: + - "tapas-tasks/**" + pull_request: + branches: [main, dev] + paths: + - "tapas-tasks/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f tapas-tasks/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=SCS-ASSE-FS21-Group1_tapas diff --git a/assignment/pom.xml b/assignment/pom.xml index e58d10a..43af6c3 100644 --- a/assignment/pom.xml +++ b/assignment/pom.xml @@ -15,6 +15,8 @@ Demo project for Spring Boot 11 + scs-asse-fs21-group1 + https://sonarcloud.io diff --git a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java index 30d7782..749e593 100644 --- a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java +++ b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java @@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class AssignmentApplication { public static void main(String[] args) { + System.out.println("TEST SONAR"); SpringApplication.run(AssignmentApplication.class, args); } diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml index d96a76d..dea007e 100644 --- a/executor-pool/pom.xml +++ b/executor-pool/pom.xml @@ -15,6 +15,8 @@ Executor Pool 11 + scs-asse-fs21-group1 + https://sonarcloud.io diff --git a/executor1/pom.xml b/executor1/pom.xml index cdadeff..1534025 100644 --- a/executor1/pom.xml +++ b/executor1/pom.xml @@ -15,6 +15,8 @@ Demo project for Spring Boot 11 + scs-asse-fs21-group1 + https://sonarcloud.io diff --git a/executor2/pom.xml b/executor2/pom.xml index d3e7318..681cebd 100644 --- a/executor2/pom.xml +++ b/executor2/pom.xml @@ -15,6 +15,8 @@ Demo project for Spring Boot 11 + scs-asse-fs21-group1 + https://sonarcloud.io diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 503984f..f3989f4 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -15,6 +15,8 @@ TAPAS Tasks 11 + scs-asse-fs21-group1 + https://sonarcloud.io -- 2.45.1 From cb47ece96c2c29162a67a0285506fb448c60957e Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 Oct 2021 18:40:27 +0200 Subject: [PATCH 05/94] fix code quality issues --- .github/workflows/ci.assignment.yml | 4 ++-- .github/workflows/ci.tapas-tasks.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.assignment.yml b/.github/workflows/ci.assignment.yml index 32b8c58..f7a1d13 100644 --- a/.github/workflows/ci.assignment.yml +++ b/.github/workflows/ci.assignment.yml @@ -1,4 +1,4 @@ -name: Build +name: CI Assignment on: push: branches: [main, dev] @@ -38,4 +38,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f assignment/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=SCS-ASSE-FS21-Group1_tapas + run: mvn -f assignment/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-assignment diff --git a/.github/workflows/ci.tapas-tasks.yml b/.github/workflows/ci.tapas-tasks.yml index c3970a0..2ddd621 100644 --- a/.github/workflows/ci.tapas-tasks.yml +++ b/.github/workflows/ci.tapas-tasks.yml @@ -1,4 +1,4 @@ -name: Build +name: CI Tasks on: push: branches: [main, dev] @@ -38,4 +38,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f tapas-tasks/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=SCS-ASSE-FS21-Group1_tapas + run: mvn -f tapas-tasks/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-tasks -- 2.45.1 From 17e770ea3846a43fe281619eb63c682487b3d34a Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 Oct 2021 18:47:53 +0200 Subject: [PATCH 06/94] added remaining ci files --- .github/workflows/ci.assignment.yml | 4 +- .github/workflows/ci.executor-pool.yml | 41 +++++++++++++++++++ .github/workflows/ci.executor1.yml | 41 +++++++++++++++++++ .github/workflows/ci.executor2.yml | 41 +++++++++++++++++++ .../ch/unisg/executorpool/TestController.java | 1 + 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.executor-pool.yml create mode 100644 .github/workflows/ci.executor1.yml create mode 100644 .github/workflows/ci.executor2.yml diff --git a/.github/workflows/ci.assignment.yml b/.github/workflows/ci.assignment.yml index f7a1d13..394fe54 100644 --- a/.github/workflows/ci.assignment.yml +++ b/.github/workflows/ci.assignment.yml @@ -3,11 +3,11 @@ on: push: branches: [main, dev] paths: - - "tapas-tasks/**" + - "assignment/**" pull_request: branches: [main, dev] paths: - - "tapas-tasks/**" + - "assignment/**" workflow_dispatch: jobs: diff --git a/.github/workflows/ci.executor-pool.yml b/.github/workflows/ci.executor-pool.yml new file mode 100644 index 0000000..e22a171 --- /dev/null +++ b/.github/workflows/ci.executor-pool.yml @@ -0,0 +1,41 @@ +name: CI Executor Pool +on: + push: + branches: [main, dev] + paths: + - "executor-pool/**" + pull_request: + branches: [main, dev] + paths: + - "executor-pool/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f executor-pool/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor-pool diff --git a/.github/workflows/ci.executor1.yml b/.github/workflows/ci.executor1.yml new file mode 100644 index 0000000..6ae1ab9 --- /dev/null +++ b/.github/workflows/ci.executor1.yml @@ -0,0 +1,41 @@ +name: CI Tasks +on: + push: + branches: [main, dev] + paths: + - "executor1/**" + pull_request: + branches: [main, dev] + paths: + - "executor1/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f executor1/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor1 diff --git a/.github/workflows/ci.executor2.yml b/.github/workflows/ci.executor2.yml new file mode 100644 index 0000000..d896cd5 --- /dev/null +++ b/.github/workflows/ci.executor2.yml @@ -0,0 +1,41 @@ +name: CI Tasks +on: + push: + branches: [main, dev] + paths: + - "executor2/**" + pull_request: + branches: [main, dev] + paths: + - "executor2/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f executor2/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor2 diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java index ca29e09..c282f79 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RestController; public class TestController { @RequestMapping("/") public String index() { + System.out.println("TEST"); return "Hello World! Executor Pool"; } } -- 2.45.1 From 050d11254c00375b38e7bd28c46449ed1c2967ef Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 7 Oct 2021 10:02:35 +0200 Subject: [PATCH 07/94] naming & bug fixing --- .github/workflows/ci.executor1.yml | 2 +- .github/workflows/ci.executor2.yml | 2 +- .../main/java/ch/unisg/assignment/AssignmentApplication.java | 1 - .../src/main/java/ch/unisg/executorpool/TestController.java | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.executor1.yml b/.github/workflows/ci.executor1.yml index 6ae1ab9..277d313 100644 --- a/.github/workflows/ci.executor1.yml +++ b/.github/workflows/ci.executor1.yml @@ -1,4 +1,4 @@ -name: CI Tasks +name: CI Executor 1 on: push: branches: [main, dev] diff --git a/.github/workflows/ci.executor2.yml b/.github/workflows/ci.executor2.yml index d896cd5..d763cd1 100644 --- a/.github/workflows/ci.executor2.yml +++ b/.github/workflows/ci.executor2.yml @@ -1,4 +1,4 @@ -name: CI Tasks +name: CI Executor 2 on: push: branches: [main, dev] diff --git a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java index 749e593..30d7782 100644 --- a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java +++ b/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java @@ -7,7 +7,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class AssignmentApplication { public static void main(String[] args) { - System.out.println("TEST SONAR"); SpringApplication.run(AssignmentApplication.class, args); } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java index c282f79..ca29e09 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.RestController; public class TestController { @RequestMapping("/") public String index() { - System.out.println("TEST"); return "Hello World! Executor Pool"; } } -- 2.45.1 From 1d78bb63abb03a7c4898ed5f98372b9df3153064 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 Oct 2021 19:21:22 +0200 Subject: [PATCH 08/94] basic implementation of an executor --- .editorconfig | 12 +++ executor1/pom.xml | 16 ++++ .../unisg/executor1/Executor1Application.java | 3 + .../ch/unisg/executor1/TestController.java | 12 --- .../executor1/common/SelfValidating.java | 29 ++++++ .../in/web/TaskAvailableController.java | 32 +++++++ .../out/web/ExecutionFinishedAdapter.java | 58 ++++++++++++ .../adapter/out/web/GetAssignmentAdapter.java | 44 +++++++++ .../out/web/NotifyExecutorPoolAdapter.java | 61 ++++++++++++ .../port/in/TaskAvailableCommand.java | 19 ++++ .../port/in/TaskAvailableUseCase.java | 5 + .../port/out/ExecutionFinishedEventPort.java | 7 ++ .../port/out/GetAssignmentPort.java | 7 ++ .../port/out/NotifyExecutorPoolPort.java | 5 + .../service/NotifyExecutorPoolService.java | 17 ++++ .../service/TaskAvailableService.java | 27 ++++++ .../domain/ExecutionFinishedEvent.java | 21 +++++ .../executor1/executor/domain/Executor.java | 93 +++++++++++++++++++ .../executor/domain/ExecutorStatus.java | 7 ++ .../unisg/executor1/executor/domain/Task.java | 20 ++++ .../tapastasks/TapasTasksApplication.java | 4 +- 21 files changed, 485 insertions(+), 14 deletions(-) create mode 100644 .editorconfig delete mode 100644 executor1/src/main/java/ch/unisg/executor1/TestController.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74c6104 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{java}] +indent_style = space +indent_size = 4 +tab_width = 4 +trim_trailing_whitespace = true +max_line_length = 100 diff --git a/executor1/pom.xml b/executor1/pom.xml index 1534025..8df7a04 100644 --- a/executor1/pom.xml +++ b/executor1/pom.xml @@ -30,6 +30,10 @@ runtime true + + org.springframework.boot + spring-boot-starter-validation + org.projectlombok lombok @@ -40,6 +44,18 @@ spring-boot-starter-test test + + + javax.validation + validation-api + 1.1.0.Final + + + + javax.transaction + javax.transaction-api + 1.2 + diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java index 4eed560..dfb8d8c 100644 --- a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java +++ b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java @@ -3,11 +3,14 @@ package ch.unisg.executor1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import ch.unisg.executor1.executor.domain.Executor; + @SpringBootApplication public class Executor1Application { public static void main(String[] args) { SpringApplication.run(Executor1Application.class, args); + Executor.getExecutor(); } } diff --git a/executor1/src/main/java/ch/unisg/executor1/TestController.java b/executor1/src/main/java/ch/unisg/executor1/TestController.java deleted file mode 100644 index a2f32b4..0000000 --- a/executor1/src/main/java/ch/unisg/executor1/TestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unisg.executor1; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TestController { - @RequestMapping("/") - public String index() { - return "Hello World! Executor1"; - } -} diff --git a/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java b/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java new file mode 100644 index 0000000..bc9816a --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java @@ -0,0 +1,29 @@ +package ch.unisg.executor1.common; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import java.util.Set; + +public class SelfValidating { + + private Validator validator; + + public SelfValidating() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Evaluates all Bean Validations on the attributes of this + * instance. + */ + protected void validateSelf() { + Set> violations = validator.validate((T) this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java new file mode 100644 index 0000000..14dc3e6 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -0,0 +1,32 @@ +package ch.unisg.executor1.executor.adapter.in.web; + +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.executor1.executor.application.port.in.TaskAvailableCommand; +import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; + +@RestController +public class TaskAvailableController { + private final TaskAvailableUseCase taskAvailableUseCase; + + public TaskAvailableController(TaskAvailableUseCase taskAvailableUseCase) { + this.taskAvailableUseCase = taskAvailableUseCase; + } + + @GetMapping(path = "/newtask/{taskType}") + public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { + TaskAvailableCommand command = new TaskAvailableCommand(taskType); + + taskAvailableUseCase.newTaskAvailable(command); + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + + return new ResponseEntity<>("OK", responseHeaders, HttpStatus.OK); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java new file mode 100644 index 0000000..e1c3a5d --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java @@ -0,0 +1,58 @@ +package ch.unisg.executor1.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.HashMap; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; + +import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; + +public class ExecutionFinishedAdapter implements ExecutionFinishedEventPort { + + //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) + String server = "http://127.0.0.1:8082"; + + @Override + public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { + ///Here we would need to work with DTOs in case the payload of calls becomes more complex + + var values = new HashMap() {{ + put("result",event.getResult()); + put("status",event.getStatus()); + }}; + + var objectMapper = new ObjectMapper(); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(values); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/task/"+event.getTaskID())) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + System.out.println("Finish execution event sent"); + + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java new file mode 100644 index 0000000..2c32f84 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java @@ -0,0 +1,44 @@ +package ch.unisg.executor1.executor.adapter.out.web; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.executor.domain.Task; + +@Component +@Primary +public class GetAssignmentAdapter implements GetAssignmentPort { + + //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) + String server = "http://127.0.0.1:8082"; + + @Override + public Task getAssignment(String executorType) { + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/assignment/" + executorType)) + .GET() + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + // TODO return null or a new Task here depending on the response of the http call + + return new Task("1234"); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java new file mode 100644 index 0000000..866ab91 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -0,0 +1,61 @@ +package ch.unisg.executor1.executor.adapter.out.web; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.util.HashMap; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; + +@Component +@Primary +public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { + + //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) + String server = "http://127.0.0.1:8083"; + + @Override + public boolean notifyExecutorPool(String ip, int port, String executorType) { + + var values = new HashMap() {{ + put("ip", ip); + put("port", Integer.toString(port)); + put("executorType", executorType); + }}; + + var objectMapper = new ObjectMapper(); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(values); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/executor/new/")) + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + // TODO return true or false depending on result of http request; + + return true; + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java new file mode 100644 index 0000000..a5d530d --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java @@ -0,0 +1,19 @@ +package ch.unisg.executor1.executor.application.port.in; + +import ch.unisg.executor1.common.SelfValidating; + +import javax.validation.constraints.NotNull; + +import lombok.Value; + +@Value +public class TaskAvailableCommand extends SelfValidating { + + @NotNull + private final String taskType; + + public TaskAvailableCommand(String taskType) { + this.taskType = taskType; + this.validateSelf(); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java new file mode 100644 index 0000000..d8184c1 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.in; + +public interface TaskAvailableUseCase { + void newTaskAvailable(TaskAvailableCommand command); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java new file mode 100644 index 0000000..6bfea70 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.application.port.out; + +import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; + +public interface ExecutionFinishedEventPort { + void publishExecutionFinishedEvent(ExecutionFinishedEvent event); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java new file mode 100644 index 0000000..bbf9124 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.application.port.out; + +import ch.unisg.executor1.executor.domain.Task; + +public interface GetAssignmentPort { + Task getAssignment(String executorType); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java new file mode 100644 index 0000000..856163f --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.out; + +public interface NotifyExecutorPoolPort { + boolean notifyExecutorPool(String ip, int port, String executorType); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java new file mode 100644 index 0000000..0c05fda --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java @@ -0,0 +1,17 @@ +package ch.unisg.executor1.executor.application.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class NotifyExecutorPoolService { + + private final NotifyExecutorPoolPort notifyExecutorPoolPort; + + public boolean notifyExecutorPool(String ip, int port, String executorType) { + return notifyExecutorPoolPort.notifyExecutorPool(ip, port, executorType); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java new file mode 100644 index 0000000..795cd8b --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -0,0 +1,27 @@ +package ch.unisg.executor1.executor.application.service; + +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.in.TaskAvailableCommand; +import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; +import ch.unisg.executor1.executor.domain.Executor; +import ch.unisg.executor1.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().equalsIgnoreCase(command.getTaskType()) && + executor.getStatus() == ExecutorStatus.IDLING) { + executor.getAssignment(); + } + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java new file mode 100644 index 0000000..4455572 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java @@ -0,0 +1,21 @@ +package ch.unisg.executor1.executor.domain; + +import lombok.Getter; + +public class ExecutionFinishedEvent { + + @Getter + private String taskID; + + @Getter + private String result; + + @Getter + private String status; + + public ExecutionFinishedEvent(String taskID, String result, String status) { + this.taskID = taskID; + this.result = result; + this.status = status; + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java new file mode 100644 index 0000000..6329f29 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -0,0 +1,93 @@ +package ch.unisg.executor1.executor.domain; + +import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; + +import java.util.concurrent.TimeUnit; + +import javax.transaction.Transactional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; + +import ch.unisg.executor1.executor.adapter.out.web.ExecutionFinishedAdapter; +import ch.unisg.executor1.executor.adapter.out.web.GetAssignmentAdapter; +import ch.unisg.executor1.executor.adapter.out.web.NotifyExecutorPoolAdapter; +import ch.unisg.executor1.executor.application.service.NotifyExecutorPoolService; +import lombok.Getter; + +public class Executor { + + @Getter + private String ip; + + @Getter + private String executorType = "addition"; + + @Getter + private int port; + + @Getter + private ExecutorStatus status; + + private static final Executor executor = new Executor(); + + // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model + // TODO I guess we can somehow autowire this but I don't know why it's not working :D + private final NotifyExecutorPoolPort notifyExecutorPoolPort = new NotifyExecutorPoolAdapter(); + private final NotifyExecutorPoolService notifyExecutorPoolService = new NotifyExecutorPoolService(notifyExecutorPoolPort); + private final GetAssignmentPort getAssignmentPort = new GetAssignmentAdapter(); + private final ExecutionFinishedEventPort executionFinishedEventPort = new ExecutionFinishedAdapter(); + + private Executor() { + System.out.println("Starting Executor"); + // TODO set this automaticly + this.ip = "localhost"; + this.port = 8084; + + this.status = ExecutorStatus.STARTING_UP; + if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { + System.exit(0); + } else { + System.out.println(true); + this.status = ExecutorStatus.IDLING; + getAssignment(); + } + } + + public static Executor getExecutor() { + return executor; + } + + public void getAssignment() { + Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); + if (newTask != null) { + this.executeTask(newTask); + } else { + this.status = ExecutorStatus.IDLING; + } + } + + private void executeTask(Task task) { + System.out.println("Starting execution"); + this.status = ExecutorStatus.EXECUTING; + int a = 10; + int b = 20; + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + int result = a + b; + + task.setResult(Integer.toString(result)); + + executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); + + System.out.println("Finish execution"); + getAssignment(); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java new file mode 100644 index 0000000..92af024 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.domain; + +public enum ExecutorStatus { + STARTING_UP, + EXECUTING, + IDLING, +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java new file mode 100644 index 0000000..2287b6c --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java @@ -0,0 +1,20 @@ +package ch.unisg.executor1.executor.domain; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +public class Task { + + @Getter + private String taskID; + + @Getter + @Setter + private String result; + + public Task(String taskID) { + this.taskID = taskID; + } + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java index 40fa5da..90d1716 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java @@ -10,8 +10,8 @@ public class TapasTasksApplication { public static void main(String[] args) { - SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); - tapasTasksApp.run(args); + SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); + tapasTasksApp.run(args); } } -- 2.45.1 From 3bc39b70aa6e62ee52edc375f97c74c4a73ccaef Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 Oct 2021 21:20:45 +0200 Subject: [PATCH 09/94] Added abstraction --- .../in/web/TaskAvailableController.java | 10 ++- ...ava => ExecutionFinishedEventAdapter.java} | 4 +- .../adapter/out/web/GetAssignmentAdapter.java | 5 +- .../out/web/NotifyExecutorPoolAdapter.java | 5 +- .../port/in/TaskAvailableCommand.java | 5 +- .../port/out/GetAssignmentPort.java | 3 +- .../port/out/NotifyExecutorPoolPort.java | 4 +- .../service/NotifyExecutorPoolService.java | 3 +- .../service/TaskAvailableService.java | 2 +- .../executor1/executor/domain/Executor.java | 78 ++--------------- .../executor/domain/ExecutorBase.java | 87 +++++++++++++++++++ .../executor/domain/ExecutorType.java | 18 ++++ 12 files changed, 140 insertions(+), 84 deletions(-) rename executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/{ExecutionFinishedAdapter.java => ExecutionFinishedEventAdapter.java} (91%) create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java index 14dc3e6..eecc4a3 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RestController; import ch.unisg.executor1.executor.application.port.in.TaskAvailableCommand; import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; +import ch.unisg.executor1.executor.domain.ExecutorType; @RestController public class TaskAvailableController { @@ -20,10 +21,13 @@ public class TaskAvailableController { @GetMapping(path = "/newtask/{taskType}") public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { - TaskAvailableCommand command = new TaskAvailableCommand(taskType); - - taskAvailableUseCase.newTaskAvailable(command); + if (ExecutorType.contains(taskType.toUpperCase())) { + TaskAvailableCommand command = new TaskAvailableCommand( + ExecutorType.valueOf(taskType.toUpperCase())); + taskAvailableUseCase.newTaskAvailable(command); + } + // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java similarity index 91% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java rename to executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index e1c3a5d..41e68a6 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; -public class ExecutionFinishedAdapter implements ExecutionFinishedEventPort { +public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) String server = "http://127.0.0.1:8082"; @@ -51,7 +51,7 @@ public class ExecutionFinishedAdapter implements ExecutionFinishedEventPort { } **/ - System.out.println("Finish execution event sent"); + System.out.println("Finish execution event sent with result:" + event.getResult()); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java index 2c32f84..c7d8485 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.executor.domain.ExecutorType; import ch.unisg.executor1.executor.domain.Task; @Component @@ -18,7 +19,7 @@ public class GetAssignmentAdapter implements GetAssignmentPort { String server = "http://127.0.0.1:8082"; @Override - public Task getAssignment(String executorType) { + public Task getAssignment(ExecutorType executorType) { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() @@ -38,7 +39,7 @@ public class GetAssignmentAdapter implements GetAssignmentPort { // TODO return null or a new Task here depending on the response of the http call - return new Task("1234"); + return null; } } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index 866ab91..d7a9238 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -12,6 +12,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; +import ch.unisg.executor1.executor.domain.ExecutorType; @Component @Primary @@ -21,12 +22,12 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { String server = "http://127.0.0.1:8083"; @Override - public boolean notifyExecutorPool(String ip, int port, String executorType) { + public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { var values = new HashMap() {{ put("ip", ip); put("port", Integer.toString(port)); - put("executorType", executorType); + put("executorType", executorType.toString()); }}; var objectMapper = new ObjectMapper(); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java index a5d530d..292d18b 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java @@ -1,6 +1,7 @@ package ch.unisg.executor1.executor.application.port.in; import ch.unisg.executor1.common.SelfValidating; +import ch.unisg.executor1.executor.domain.ExecutorType; import javax.validation.constraints.NotNull; @@ -10,9 +11,9 @@ import lombok.Value; public class TaskAvailableCommand extends SelfValidating { @NotNull - private final String taskType; + private final ExecutorType taskType; - public TaskAvailableCommand(String taskType) { + public TaskAvailableCommand(ExecutorType taskType) { this.taskType = taskType; this.validateSelf(); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java index bbf9124..7b81b13 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java @@ -1,7 +1,8 @@ package ch.unisg.executor1.executor.application.port.out; +import ch.unisg.executor1.executor.domain.ExecutorType; import ch.unisg.executor1.executor.domain.Task; public interface GetAssignmentPort { - Task getAssignment(String executorType); + Task getAssignment(ExecutorType executorType); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java index 856163f..4086258 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java @@ -1,5 +1,7 @@ package ch.unisg.executor1.executor.application.port.out; +import ch.unisg.executor1.executor.domain.ExecutorType; + public interface NotifyExecutorPoolPort { - boolean notifyExecutorPool(String ip, int port, String executorType); + boolean notifyExecutorPool(String ip, int port, ExecutorType executorType); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java index 0c05fda..acfae94 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; +import ch.unisg.executor1.executor.domain.ExecutorType; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -11,7 +12,7 @@ public class NotifyExecutorPoolService { private final NotifyExecutorPoolPort notifyExecutorPoolPort; - public boolean notifyExecutorPool(String ip, int port, String executorType) { + public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { return notifyExecutorPoolPort.notifyExecutorPool(ip, port, executorType); } } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java index 795cd8b..6f15b47 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -19,7 +19,7 @@ public class TaskAvailableService implements TaskAvailableUseCase { public void newTaskAvailable(TaskAvailableCommand command) { Executor executor = Executor.getExecutor(); - if (executor.getExecutorType().equalsIgnoreCase(command.getTaskType()) && + if (executor.getExecutorType() == command.getTaskType() && executor.getStatus() == ExecutorStatus.IDLING) { executor.getAssignment(); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java index 6329f29..d8f9e0b 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -1,77 +1,22 @@ package ch.unisg.executor1.executor.domain; -import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; -import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; -import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; - import java.util.concurrent.TimeUnit; -import javax.transaction.Transactional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; - -import ch.unisg.executor1.executor.adapter.out.web.ExecutionFinishedAdapter; -import ch.unisg.executor1.executor.adapter.out.web.GetAssignmentAdapter; -import ch.unisg.executor1.executor.adapter.out.web.NotifyExecutorPoolAdapter; -import ch.unisg.executor1.executor.application.service.NotifyExecutorPoolService; -import lombok.Getter; - -public class Executor { - - @Getter - private String ip; - - @Getter - private String executorType = "addition"; - - @Getter - private int port; - - @Getter - private ExecutorStatus status; - - private static final Executor executor = new Executor(); - - // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model - // TODO I guess we can somehow autowire this but I don't know why it's not working :D - private final NotifyExecutorPoolPort notifyExecutorPoolPort = new NotifyExecutorPoolAdapter(); - private final NotifyExecutorPoolService notifyExecutorPoolService = new NotifyExecutorPoolService(notifyExecutorPoolPort); - private final GetAssignmentPort getAssignmentPort = new GetAssignmentAdapter(); - private final ExecutionFinishedEventPort executionFinishedEventPort = new ExecutionFinishedAdapter(); - - private Executor() { - System.out.println("Starting Executor"); - // TODO set this automaticly - this.ip = "localhost"; - this.port = 8084; +public class Executor extends ExecutorBase { - this.status = ExecutorStatus.STARTING_UP; - if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { - System.exit(0); - } else { - System.out.println(true); - this.status = ExecutorStatus.IDLING; - getAssignment(); - } - } + private static final Executor executor = new Executor(ExecutorType.ADDITION); public static Executor getExecutor() { return executor; } - public void getAssignment() { - Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); - if (newTask != null) { - this.executeTask(newTask); - } else { - this.status = ExecutorStatus.IDLING; - } + private Executor(ExecutorType executorType) { + super(executorType); } - private void executeTask(Task task) { - System.out.println("Starting execution"); - this.status = ExecutorStatus.EXECUTING; + @Override + String execution() { + int a = 10; int b = 20; try { @@ -82,12 +27,7 @@ public class Executor { int result = a + b; - task.setResult(Integer.toString(result)); - - executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); - - System.out.println("Finish execution"); - getAssignment(); + return Integer.toString(result); } - + } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java new file mode 100644 index 0000000..fe65913 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java @@ -0,0 +1,87 @@ +package ch.unisg.executor1.executor.domain; + +import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; + +import java.util.concurrent.TimeUnit; + +import javax.transaction.Transactional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; + +import ch.unisg.executor1.executor.adapter.out.web.ExecutionFinishedEventAdapter; +import ch.unisg.executor1.executor.adapter.out.web.GetAssignmentAdapter; +import ch.unisg.executor1.executor.adapter.out.web.NotifyExecutorPoolAdapter; +import ch.unisg.executor1.executor.application.service.NotifyExecutorPoolService; +import lombok.Getter; + +abstract class ExecutorBase { + + @Getter + private String ip; + + @Getter + private ExecutorType executorType; + + @Getter + private int port; + + @Getter + private ExecutorStatus status; + + // private static final ExecutorBase executor = new ExecutorBase(); + + // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model + // TODO I guess we can somehow autowire this but I don't know why it's not working :D + 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(); + + public ExecutorBase(ExecutorType executorType) { + System.out.println("Starting Executor"); + // TODO set this automaticly + this.ip = "localhost"; + this.port = 8084; + this.executorType = executorType; + + this.status = ExecutorStatus.STARTING_UP; + if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { + System.exit(0); + } else { + System.out.println(true); + this.status = ExecutorStatus.IDLING; + getAssignment(); + } + } + + // public static ExecutorBase getExecutor() { + // return executor; + // } + + public void getAssignment() { + Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); + if (newTask != null) { + this.executeTask(newTask); + } else { + this.status = ExecutorStatus.IDLING; + } + } + + private void executeTask(Task task) { + System.out.println("Starting execution"); + this.status = ExecutorStatus.EXECUTING; + + task.setResult(execution()); + + executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); + + System.out.println("Finish execution"); + getAssignment(); + } + + abstract String execution(); + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java new file mode 100644 index 0000000..58c9d91 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java @@ -0,0 +1,18 @@ +package ch.unisg.executor1.executor.domain; + +public enum ExecutorType { + ADDITION, ROBOT; + + public static boolean contains(String test) { + + for (ExecutorType x : ExecutorType.values()) { + if (x.name().equals(test)) { + return true; + } + } + return false; + } +} + + + -- 2.45.1 From 5e8434d679b8c114515e781c4b4576a1acdd35d3 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 13 Oct 2021 12:40:24 +0200 Subject: [PATCH 10/94] refactored executor1 to have base classes in own project --- {executor1 => executor-base}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {executor1 => executor-base}/Dockerfile | 0 {executor1 => executor-base}/mvnw | 0 {executor1 => executor-base}/mvnw.cmd | 0 {executor1 => executor-base}/pom.xml | 4 ++-- .../executorBase}/Executor1Application.java | 5 +--- .../executorBase}/common/SelfValidating.java | 2 +- .../in/web/TaskAvailableController.java | 8 +++---- .../web/ExecutionFinishedEventAdapter.java | 6 ++--- .../adapter/out/web/GetAssignmentAdapter.java | 10 ++++---- .../out/web/NotifyExecutorPoolAdapter.java | 6 ++--- .../port/in/TaskAvailableCommand.java | 6 ++--- .../port/in/TaskAvailableUseCase.java | 2 +- .../port/out/ExecutionFinishedEventPort.java | 7 ++++++ .../port/out/GetAssignmentPort.java | 8 +++++++ .../port/out/NotifyExecutorPoolPort.java | 4 ++-- .../service/NotifyExecutorPoolService.java | 9 +++---- .../domain/ExecutionFinishedEvent.java | 2 +- .../executor/domain/ExecutorBase.java | 22 ++++++++---------- .../executor/domain/ExecutorStatus.java | 2 +- .../executor/domain/ExecutorType.java | 2 +- .../executorBase}/executor/domain/Task.java | 2 +- .../src/main/resources/application.properties | 0 .../Executor1ApplicationTests.java | 4 ++-- .../port/out/ExecutionFinishedEventPort.java | 7 ------ .../port/out/GetAssignmentPort.java | 8 ------- executor2/pom.xml | 5 ++++ .../unisg/executor2/Executor2Application.java | 3 +++ .../ch/unisg/executor2/TestController.java | 12 ---------- .../service/TaskAvailableService.java | 11 +++++---- .../executor2}/executor/domain/Executor.java | 7 ++++-- 34 files changed, 78 insertions(+), 86 deletions(-) rename {executor1 => executor-base}/.gitignore (100%) rename {executor1 => executor-base}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {executor1 => executor-base}/.mvn/wrapper/maven-wrapper.jar (100%) rename {executor1 => executor-base}/.mvn/wrapper/maven-wrapper.properties (100%) rename {executor1 => executor-base}/Dockerfile (100%) rename {executor1 => executor-base}/mvnw (100%) rename {executor1 => executor-base}/mvnw.cmd (100%) rename {executor1 => executor-base}/pom.xml (97%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/Executor1Application.java (72%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/common/SelfValidating.java (95%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/adapter/in/web/TaskAvailableController.java (81%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/adapter/out/web/ExecutionFinishedEventAdapter.java (89%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/adapter/out/web/GetAssignmentAdapter.java (81%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/adapter/out/web/NotifyExecutorPoolAdapter.java (90%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/application/port/in/TaskAvailableCommand.java (66%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/application/port/in/TaskAvailableUseCase.java (62%) create mode 100644 executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java create mode 100644 executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/application/port/out/NotifyExecutorPoolPort.java (51%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/application/service/NotifyExecutorPoolService.java (52%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/domain/ExecutionFinishedEvent.java (88%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/domain/ExecutorBase.java (76%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/domain/ExecutorStatus.java (61%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/domain/ExecutorType.java (85%) rename {executor1/src/main/java/ch/unisg/executor1 => executor-base/src/main/java/ch/unisg/executorBase}/executor/domain/Task.java (84%) rename {executor1 => executor-base}/src/main/resources/application.properties (100%) rename {executor1/src/test/java/ch/unisg/executor1 => executor-base/src/test/java/ch/unisg/executorBase}/Executor1ApplicationTests.java (68%) delete mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java delete mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java delete mode 100644 executor2/src/main/java/ch/unisg/executor2/TestController.java rename {executor1/src/main/java/ch/unisg/executor1 => executor2/src/main/java/ch/unisg/executor2}/executor/application/service/TaskAvailableService.java (63%) rename {executor1/src/main/java/ch/unisg/executor1 => executor2/src/main/java/ch/unisg/executor2}/executor/domain/Executor.java (76%) diff --git a/executor1/.gitignore b/executor-base/.gitignore similarity index 100% rename from executor1/.gitignore rename to executor-base/.gitignore diff --git a/executor1/.mvn/wrapper/MavenWrapperDownloader.java b/executor-base/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from executor1/.mvn/wrapper/MavenWrapperDownloader.java rename to executor-base/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/executor1/.mvn/wrapper/maven-wrapper.jar b/executor-base/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from executor1/.mvn/wrapper/maven-wrapper.jar rename to executor-base/.mvn/wrapper/maven-wrapper.jar diff --git a/executor1/.mvn/wrapper/maven-wrapper.properties b/executor-base/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from executor1/.mvn/wrapper/maven-wrapper.properties rename to executor-base/.mvn/wrapper/maven-wrapper.properties diff --git a/executor1/Dockerfile b/executor-base/Dockerfile similarity index 100% rename from executor1/Dockerfile rename to executor-base/Dockerfile diff --git a/executor1/mvnw b/executor-base/mvnw similarity index 100% rename from executor1/mvnw rename to executor-base/mvnw diff --git a/executor1/mvnw.cmd b/executor-base/mvnw.cmd similarity index 100% rename from executor1/mvnw.cmd rename to executor-base/mvnw.cmd diff --git a/executor1/pom.xml b/executor-base/pom.xml similarity index 97% rename from executor1/pom.xml rename to executor-base/pom.xml index 8df7a04..6ad675a 100644 --- a/executor1/pom.xml +++ b/executor-base/pom.xml @@ -9,9 +9,9 @@ ch.unisg - executor1 + executorBase 0.0.1-SNAPSHOT - executor1 + executorBase Demo project for Spring Boot 11 diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java similarity index 72% rename from executor1/src/main/java/ch/unisg/executor1/Executor1Application.java rename to executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java index dfb8d8c..9bd3fa5 100644 --- a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java @@ -1,16 +1,13 @@ -package ch.unisg.executor1; +package ch.unisg.executorBase; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import ch.unisg.executor1.executor.domain.Executor; - @SpringBootApplication public class Executor1Application { public static void main(String[] args) { SpringApplication.run(Executor1Application.class, args); - Executor.getExecutor(); } } diff --git a/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java b/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java similarity index 95% rename from executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java rename to executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java index bc9816a..1d4a80a 100644 --- a/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.common; +package ch.unisg.executorBase.common; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java similarity index 81% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java index eecc4a3..75d3a02 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.in.web; +package ch.unisg.executorBase.executor.adapter.in.web; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -7,9 +7,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; -import ch.unisg.executor1.executor.application.port.in.TaskAvailableCommand; -import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; -import ch.unisg.executor1.executor.domain.ExecutorType; +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 { diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java similarity index 89% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 41e68a6..2f53a2b 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out.web; +package ch.unisg.executorBase.executor.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -10,8 +10,8 @@ import java.util.HashMap; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; -import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; -import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; +import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java similarity index 81% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index c7d8485..b3e7875 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out.web; +package ch.unisg.executorBase.executor.adapter.out.web; import java.net.URI; import java.net.http.HttpClient; @@ -7,9 +7,9 @@ import java.net.http.HttpRequest; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; -import ch.unisg.executor1.executor.domain.ExecutorType; -import ch.unisg.executor1.executor.domain.Task; +import ch.unisg.executorBase.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.executorBase.executor.domain.Task; @Component @Primary @@ -39,7 +39,7 @@ public class GetAssignmentAdapter implements GetAssignmentPort { // TODO return null or a new Task here depending on the response of the http call - return null; + return new Task("123"); } } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java similarity index 90% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index d7a9238..feeca69 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out.web; +package ch.unisg.executorBase.executor.adapter.out.web; import java.net.URI; import java.net.http.HttpClient; @@ -11,8 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; -import ch.unisg.executor1.executor.domain.ExecutorType; +import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; +import ch.unisg.executorBase.executor.domain.ExecutorType; @Component @Primary diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java similarity index 66% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java index 292d18b..916c8eb 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java @@ -1,7 +1,7 @@ -package ch.unisg.executor1.executor.application.port.in; +package ch.unisg.executorBase.executor.application.port.in; -import ch.unisg.executor1.common.SelfValidating; -import ch.unisg.executor1.executor.domain.ExecutorType; +import ch.unisg.executorBase.common.SelfValidating; +import ch.unisg.executorBase.executor.domain.ExecutorType; import javax.validation.constraints.NotNull; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java similarity index 62% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java index d8184c1..cc5215f 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.application.port.in; +package ch.unisg.executorBase.executor.application.port.in; public interface TaskAvailableUseCase { void newTaskAvailable(TaskAvailableCommand command); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java new file mode 100644 index 0000000..1bf668e --- /dev/null +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executorBase.executor.application.port.out; + +import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; + +public interface ExecutionFinishedEventPort { + void publishExecutionFinishedEvent(ExecutionFinishedEvent event); +} diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java new file mode 100644 index 0000000..1f205b8 --- /dev/null +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java @@ -0,0 +1,8 @@ +package ch.unisg.executorBase.executor.application.port.out; + +import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.executorBase.executor.domain.Task; + +public interface GetAssignmentPort { + Task getAssignment(ExecutorType executorType); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java similarity index 51% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java index 4086258..6d41ab4 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java @@ -1,6 +1,6 @@ -package ch.unisg.executor1.executor.application.port.out; +package ch.unisg.executorBase.executor.application.port.out; -import ch.unisg.executor1.executor.domain.ExecutorType; +import ch.unisg.executorBase.executor.domain.ExecutorType; public interface NotifyExecutorPoolPort { boolean notifyExecutorPool(String ip, int port, ExecutorType executorType); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java similarity index 52% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java index acfae94..a5ccb64 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java @@ -1,10 +1,7 @@ -package ch.unisg.executor1.executor.application.service; +package ch.unisg.executorBase.executor.application.service; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; -import ch.unisg.executor1.executor.domain.ExecutorType; +import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; +import ch.unisg.executorBase.executor.domain.ExecutorType; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java similarity index 88% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java index 4455572..31fd0e6 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorBase.executor.domain; import lombok.Getter; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java similarity index 76% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index fe65913..e639cb3 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -1,8 +1,8 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorBase.executor.domain; -import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; -import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; -import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; +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 java.util.concurrent.TimeUnit; @@ -11,13 +11,13 @@ import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; -import ch.unisg.executor1.executor.adapter.out.web.ExecutionFinishedEventAdapter; -import ch.unisg.executor1.executor.adapter.out.web.GetAssignmentAdapter; -import ch.unisg.executor1.executor.adapter.out.web.NotifyExecutorPoolAdapter; -import ch.unisg.executor1.executor.application.service.NotifyExecutorPoolService; +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.service.NotifyExecutorPoolService; import lombok.Getter; -abstract class ExecutorBase { +public abstract class ExecutorBase { @Getter private String ip; @@ -31,8 +31,6 @@ abstract class ExecutorBase { @Getter private ExecutorStatus status; - // private static final ExecutorBase executor = new ExecutorBase(); - // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model // TODO I guess we can somehow autowire this but I don't know why it's not working :D private final NotifyExecutorPoolPort notifyExecutorPoolPort = new NotifyExecutorPoolAdapter(); @@ -82,6 +80,6 @@ abstract class ExecutorBase { getAssignment(); } - abstract String execution(); + protected abstract String execution(); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java similarity index 61% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java index 92af024..8bd5bc3 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorBase.executor.domain; public enum ExecutorStatus { STARTING_UP, diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java similarity index 85% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java index 58c9d91..0b2b305 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorType.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorBase.executor.domain; public enum ExecutorType { ADDITION, ROBOT; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java similarity index 84% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java rename to executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java index 2287b6c..6719613 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorBase.executor.domain; import lombok.Data; import lombok.Getter; diff --git a/executor1/src/main/resources/application.properties b/executor-base/src/main/resources/application.properties similarity index 100% rename from executor1/src/main/resources/application.properties rename to executor-base/src/main/resources/application.properties diff --git a/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java b/executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java similarity index 68% rename from executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java rename to executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java index 889c9cd..6fec034 100644 --- a/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java +++ b/executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java @@ -1,10 +1,10 @@ -package ch.unisg.executor1; +package ch.unisg.executorBase; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Executor1ApplicationTests { +class executorBaseApplicationTests { @Test void contextLoads() { diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java deleted file mode 100644 index 6bfea70..0000000 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.unisg.executor1.executor.application.port.out; - -import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; - -public interface ExecutionFinishedEventPort { - void publishExecutionFinishedEvent(ExecutionFinishedEvent event); -} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java deleted file mode 100644 index 7b81b13..0000000 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java +++ /dev/null @@ -1,8 +0,0 @@ -package ch.unisg.executor1.executor.application.port.out; - -import ch.unisg.executor1.executor.domain.ExecutorType; -import ch.unisg.executor1.executor.domain.Task; - -public interface GetAssignmentPort { - Task getAssignment(ExecutorType executorType); -} diff --git a/executor2/pom.xml b/executor2/pom.xml index 681cebd..95c67ff 100644 --- a/executor2/pom.xml +++ b/executor2/pom.xml @@ -40,6 +40,11 @@ spring-boot-starter-test test + + ch.unisg + executorBase + 0.0.1-SNAPSHOT + diff --git a/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java b/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java index d31e277..03edb3d 100644 --- a/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java +++ b/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java @@ -3,11 +3,14 @@ package ch.unisg.executor2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import ch.unisg.executor2.executor.domain.Executor; + @SpringBootApplication public class Executor2Application { public static void main(String[] args) { SpringApplication.run(Executor2Application.class, args); + Executor.getExecutor(); } } diff --git a/executor2/src/main/java/ch/unisg/executor2/TestController.java b/executor2/src/main/java/ch/unisg/executor2/TestController.java deleted file mode 100644 index c98bb02..0000000 --- a/executor2/src/main/java/ch/unisg/executor2/TestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unisg.executor2; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TestController { - @RequestMapping("/") - public String index() { - return "Hello World! Executor2"; - } -} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java similarity index 63% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java rename to executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 6f15b47..4484ebb 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -1,11 +1,11 @@ -package ch.unisg.executor1.executor.application.service; +package ch.unisg.executor2.executor.application.service; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.in.TaskAvailableCommand; -import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; -import ch.unisg.executor1.executor.domain.Executor; -import ch.unisg.executor1.executor.domain.ExecutorStatus; +import ch.unisg.executor2.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; @@ -17,6 +17,7 @@ public class TaskAvailableService implements TaskAvailableUseCase { @Override public void newTaskAvailable(TaskAvailableCommand command) { + Executor executor = Executor.getExecutor(); if (executor.getExecutorType() == command.getTaskType() && diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java similarity index 76% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java rename to executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index d8f9e0b..bb9308b 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -1,6 +1,8 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executor2.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 { @@ -15,9 +17,10 @@ public class Executor extends ExecutorBase { } @Override + protected String execution() { - int a = 10; + int a = 20; int b = 20; try { TimeUnit.SECONDS.sleep(10); -- 2.45.1 From a7f62a8b23b380afcfbf789b7ee5fb546acdecdd Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 13 Oct 2021 16:24:38 +0200 Subject: [PATCH 11/94] edited editorconfig --- .editorconfig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index 74c6104..c4f3e5b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,9 @@ root = true [*] -end_of_line = lf -insert_final_newline = true - -[*.{java}] +charset = utf-8 indent_style = space indent_size = 4 -tab_width = 4 trim_trailing_whitespace = true -max_line_length = 100 +end_of_line = lf +insert_final_newline = true -- 2.45.1 From 4a85548a9e78890a2f3e6d94e7f22fd0b8749957 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 Oct 2021 17:49:09 +0200 Subject: [PATCH 12/94] github actions fixes & improvements --- .github/workflows/build-and-deploy.yml | 143 ++++++++++++++----------- .github/workflows/ci.executor1.yml | 78 +++++++------- .github/workflows/ci.executor2.yml | 78 +++++++------- 3 files changed, 163 insertions(+), 136 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 43bb471..42b93ad 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -4,75 +4,94 @@ name: Build and Deploy on: - push: - branches: - - main + push: + branches: + - main - workflow_dispatch: + workflow_dispatch: jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read + build: + runs-on: ubuntu-latest + permissions: + contents: read - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: "11" - distribution: "adopt" + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: "11" + distribution: "adopt" - - run: mkdir ./target + - run: mkdir ./target - - name: Build with Maven - 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 + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 - - name: Build with Maven - run: mvn -f app/pom.xml --batch-mode --update-snapshots verify - - run: cp ./app/target/app-0.1.0.jar ./target + - name: Build with Maven + run: mvn -f assignment/pom.xml --batch-mode --update-snapshots verify + - run: cp ./assignment/target/assignment-0.0.1-SNAPSHOT.jar ./target - - run: cp ./.deployment/docker-compose.yml ./target - - name: Archive artifacts - uses: actions/upload-artifact@v1 - with: - name: app - path: ./target/ + - name: Build with Maven + run: mvn -f executor-pool/pom.xml --batch-mode --update-snapshots verify + - run: cp ./executor-pool/target/executor-pool-0.0.1.jar ./target - deploy: - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Download app artifacts - uses: actions/download-artifact@v1 - with: - name: app - - name: Copy host via scp - uses: appleboy/scp-action@master - env: - HOST: ${{ secrets.SSH_HOST }} - USERNAME: ${{ secrets.SSH_USER }} - PORT: 22 - KEY: ${{ secrets.SSH_PRIVATE_KEY }} - with: - source: "app/*" - target: "/home/${{ secrets.SSH_USER }}/" - strip_components: 1 - rm: false - overwrite: true + - name: Build with Maven + run: mvn -f executor1/pom.xml --batch-mode --update-snapshots verify + - run: cp ./executor1/target/executor1-0.0.1.jar ./target - - name: Executing remote command - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.SSH_HOST }} - USERNAME: ${{ secrets.SSH_USER }} - PORT: 22 - KEY: ${{ secrets.SSH_PRIVATE_KEY }} - script: | - cd /home/${{ secrets.SSH_USER }}/ - touch acme.json - sudo chmod 0600 acme.json - sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env - sudo docker-compose up -d + - name: Build with Maven + run: mvn -f executor2/pom.xml --batch-mode --update-snapshots verify + - run: cp ./executor2/target/executor2-0.0.1.jar ./target + + - name: Build with Maven + run: mvn -f tapas-tasks/pom.xml --batch-mode --update-snapshots verify + - run: cp ./tapas-tasks/target/tapas-tasks-0.0.1.jar ./target + + - run: cp ./.deployment/docker-compose.yml ./target + - name: Archive artifacts + uses: actions/upload-artifact@v1 + with: + name: app + path: ./target/ + + deploy: + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Download app artifacts + uses: actions/download-artifact@v1 + with: + name: app + - name: Copy host via scp + uses: appleboy/scp-action@master + env: + HOST: ${{ secrets.SSH_HOST }} + USERNAME: ${{ secrets.SSH_USER }} + PORT: 22 + KEY: ${{ secrets.SSH_PRIVATE_KEY }} + with: + source: "app/*" + target: "/home/${{ secrets.SSH_USER }}/" + strip_components: 1 + rm: false + overwrite: true + + - name: Executing remote command + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SSH_HOST }} + USERNAME: ${{ secrets.SSH_USER }} + PORT: 22 + KEY: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /home/${{ secrets.SSH_USER }}/ + touch acme.json + sudo chmod 0600 acme.json + sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env + sudo docker-compose up -d diff --git a/.github/workflows/ci.executor1.yml b/.github/workflows/ci.executor1.yml index 277d313..5d48580 100644 --- a/.github/workflows/ci.executor1.yml +++ b/.github/workflows/ci.executor1.yml @@ -1,41 +1,45 @@ name: CI Executor 1 on: - push: - branches: [main, dev] - paths: - - "executor1/**" - pull_request: - branches: [main, dev] - paths: - - "executor1/**" + push: + branches: [main, dev] + paths: + - "executor-base/**" + - "executor1/**" + pull_request: + branches: [main, dev] + paths: + - "executor-base/**" + - "executor1/**" - workflow_dispatch: + workflow_dispatch: jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f executor1/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor1 + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build executorBase + run: mvn -f executor-base/pom.xml -B verify + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f executor1/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor1 diff --git a/.github/workflows/ci.executor2.yml b/.github/workflows/ci.executor2.yml index d763cd1..32a59a8 100644 --- a/.github/workflows/ci.executor2.yml +++ b/.github/workflows/ci.executor2.yml @@ -1,41 +1,45 @@ name: CI Executor 2 on: - push: - branches: [main, dev] - paths: - - "executor2/**" - pull_request: - branches: [main, dev] - paths: - - "executor2/**" + push: + branches: [main, dev] + paths: + - "executor-base/**" + - "executor2/**" + pull_request: + branches: [main, dev] + paths: + - "executor-base/**" + - "executor2/**" - workflow_dispatch: + workflow_dispatch: jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f executor2/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor2 + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build executorBase + run: mvn -f executor-base/pom.xml -B verify + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f executor2/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor2 -- 2.45.1 From 6a8ce604349e173779e3a2751dd81018182f17bf Mon Sep 17 00:00:00 2001 From: jj187 Date: Sat, 16 Oct 2021 19:16:20 +0200 Subject: [PATCH 13/94] adding all changes made to task list --- .../in/web/CompleteTaskWebController.java | 41 ++++++++++++++++ .../in/web/DeleteTaskWebController.java | 48 +++++++++++++++++++ .../in/web/TaskAssignedWebController.java | 42 ++++++++++++++++ .../tasks/adapter/in/web/TaskMediaType.java | 2 +- .../PublishNewTaskAddedEventWebAdapter.java | 2 + .../port/in/CompleteTaskCommand.java | 23 +++++++++ .../port/in/CompleteTaskUseCase.java | 7 +++ .../port/in/DeleteTaskCommand.java | 18 +++++++ .../port/in/DeleteTaskUseCase.java | 9 ++++ .../port/in/TaskAssignedCommand.java | 18 +++++++ .../port/in/TaskAssignedUseCase.java | 7 +++ .../service/AddNewTaskToTaskListService.java | 2 +- .../service/CompleteTaskService.java | 35 ++++++++++++++ .../service/DeleteTaskService.java | 25 ++++++++++ .../service/TaskAssignedService.java | 31 ++++++++++++ .../tasks/domain/NewTaskAddedEvent.java | 7 ++- .../unisg/tapastasks/tasks/domain/Task.java | 12 ++++- .../tapastasks/tasks/domain/TaskList.java | 14 +++++- 18 files changed, 337 insertions(+), 6 deletions(-) create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskUseCase.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskUseCase.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedCommand.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedUseCase.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java new file mode 100644 index 0000000..e160c2b --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java @@ -0,0 +1,41 @@ +package ch.unisg.tapastasks.tasks.adapter.in.web; + +import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskCommand; +import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import javax.validation.ConstraintViolationException; + +@RestController +public class CompleteTaskWebController { + private final CompleteTaskUseCase completeTaskUseCase; + + public CompleteTaskWebController(CompleteTaskUseCase completeTaskUseCase){ + this.completeTaskUseCase = completeTaskUseCase; + } + + @PostMapping(path = "/tasks/completeTask", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) + public ResponseEntity completeTask (@RequestBody Task task){ + try { + CompleteTaskCommand command = new CompleteTaskCommand( + task.getTaskId(), task.getTaskResult() + ); + + Task updateATask = completeTaskUseCase.completeTask(command); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + + return new ResponseEntity<>(TaskMediaType.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); + } catch(ConstraintViolationException e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java new file mode 100644 index 0000000..6758337 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java @@ -0,0 +1,48 @@ +package ch.unisg.tapastasks.tasks.adapter.in.web; + + +import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskCommand; +import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import javax.validation.ConstraintViolationException; +import java.util.Optional; + +@RestController +public class DeleteTaskWebController { + private final DeleteTaskUseCase deleteClassUseCase; + + public DeleteTaskWebController(DeleteTaskUseCase deleteClassUseCase){ + this.deleteClassUseCase = deleteClassUseCase; + } + + @PostMapping(path="/tasks/deleteTask", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) + public ResponseEntity deleteTask (@RequestBody Task task){ + try { + DeleteTaskCommand command = new DeleteTaskCommand(task.getTaskId()); + + Optional deleteATask = deleteClassUseCase.deleteTask(command); + + // Check if the task with the given identifier exists + if (deleteATask.isEmpty()) { + // If not, through a 404 Not Found status code + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + + + return new ResponseEntity<>(TaskMediaType.serialize(deleteATask.get()), responseHeaders, HttpStatus.ACCEPTED); + } catch(ConstraintViolationException e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java new file mode 100644 index 0000000..9dfa6a2 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java @@ -0,0 +1,42 @@ +package ch.unisg.tapastasks.tasks.adapter.in.web; + +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedCommand; +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import javax.validation.ConstraintViolationException; + +@RestController +public class TaskAssignedWebController { + private final TaskAssignedUseCase taskAssignedUseCase; + + public TaskAssignedWebController(TaskAssignedUseCase taskAssignedUseCase){ + this.taskAssignedUseCase = taskAssignedUseCase; + } + + @PostMapping(path="/tasks/assignTask", consumes= {TaskMediaType.TASK_MEDIA_TYPE}) + public ResponseEntity assignTask(@RequestBody Task task){ + try{ + TaskAssignedCommand command = new TaskAssignedCommand( + task.getTaskId() + ); + + Task updateATask = taskAssignedUseCase.assignTask(command); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + + return new ResponseEntity<>(TaskMediaType.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); + } catch (ConstraintViolationException e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java index 3c555e5..d9a0a46 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java @@ -15,7 +15,7 @@ final public class TaskMediaType { payload.put("taskType", task.getTaskType().getValue()); payload.put("taskState", task.getTaskState().getValue()); payload.put("taskListName", TaskList.getTapasTaskList().getTaskListName().getValue()); - + payload.put("taskResult", task.getTaskResult().getValue()); return payload.toString(); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java index db02f2a..7ae8e90 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java @@ -29,6 +29,8 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort var values = new HashMap() {{ put("taskname",event.taskName); put("tasklist",event.taskListName); + put("taskId", event.taskId); + put("taskType", event.taskType); }}; var objectMapper = new ObjectMapper(); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java new file mode 100644 index 0000000..0634165 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java @@ -0,0 +1,23 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import ch.unisg.tapastasks.tasks.domain.Task.TaskResult; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +public class CompleteTaskCommand extends SelfValidating { + @NotNull + private final TaskId taskId; + + @NotNull + private final TaskResult taskResult; + + public CompleteTaskCommand(TaskId taskId, TaskResult taskResult){ + this.taskId = taskId; + this.taskResult = taskResult; + this.validateSelf(); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskUseCase.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskUseCase.java new file mode 100644 index 0000000..6fba7e6 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskUseCase.java @@ -0,0 +1,7 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface CompleteTaskUseCase { + Task completeTask(CompleteTaskCommand command); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java new file mode 100644 index 0000000..24acbb8 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java @@ -0,0 +1,18 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +public class DeleteTaskCommand extends SelfValidating { + @NotNull + private final TaskId taskId; + + public DeleteTaskCommand(TaskId taskId){ + this.taskId=taskId; + this.validateSelf(); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskUseCase.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskUseCase.java new file mode 100644 index 0000000..8ba206f --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskUseCase.java @@ -0,0 +1,9 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +import java.util.Optional; + +public interface DeleteTaskUseCase { + Optional deleteTask(DeleteTaskCommand command); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedCommand.java new file mode 100644 index 0000000..7a5e383 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedCommand.java @@ -0,0 +1,18 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +public class TaskAssignedCommand extends SelfValidating { + @NotNull + private final TaskId taskId; + + public TaskAssignedCommand(TaskId taskId){ + this.taskId=taskId; + this.validateSelf(); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedUseCase.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedUseCase.java new file mode 100644 index 0000000..3a84587 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedUseCase.java @@ -0,0 +1,7 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface TaskAssignedUseCase { + Task assignTask(TaskAssignedCommand command); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 48c75a6..f1a6a8c 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -30,7 +30,7 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase //the core and then the integration event in the application layer. if (newTask != null) { NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent(newTask.getTaskName().getValue(), - taskList.getTaskListName().getValue()); + taskList.getTaskListName().getValue(), newTask.getTaskId().getValue(), newTask.getTaskType().getValue()); newTaskAddedEventPort.publishNewTaskAddedEvent(newTaskAdded); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java new file mode 100644 index 0000000..bade832 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java @@ -0,0 +1,35 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskCommand; +import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.Task.*; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.Optional; + +@RequiredArgsConstructor +@Component +@Transactional +public class CompleteTaskService implements CompleteTaskUseCase { + + @Override + public Task completeTask(CompleteTaskCommand command){ + // TODO Retrieve the task based on ID + TaskList taskList = TaskList.getTapasTaskList(); + Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); + + // TODO Update the status and result (and save?) + Task newTask = updatedTask.get(); + newTask.taskResult = new TaskResult(command.getTaskResult().getValue()); + newTask.taskState = new TaskState(Task.State.EXECUTED); + + + // TODO return the updated task + return newTask; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java new file mode 100644 index 0000000..05d1da5 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java @@ -0,0 +1,25 @@ +package ch.unisg.tapastasks.tasks.application.service; + + +import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskCommand; +import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.Optional; + +@RequiredArgsConstructor +@Component +@Transactional +public class DeleteTaskService implements DeleteTaskUseCase { + + @Override + public Optional deleteTask(DeleteTaskCommand command){ + TaskList taskList = TaskList.getTapasTaskList(); + return taskList.deleteTaskById(command.getTaskId()); + + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java new file mode 100644 index 0000000..baa6059 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java @@ -0,0 +1,31 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedCommand; +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.Task.*; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.Optional; + +@RequiredArgsConstructor +@Component +@Transactional +public class TaskAssignedService implements TaskAssignedUseCase { + + @Override + public Task assignTask(TaskAssignedCommand command) { + // retrieve the task based on ID + TaskList taskList = TaskList.getTapasTaskList(); + Optional task = taskList.retrieveTaskById(command.getTaskId()); + + // update the status to assigned + Task updatedTask = task.get(); + updatedTask.taskState = new TaskState(State.ASSIGNED); + + return updatedTask; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java index 32f5966..a4703f2 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java @@ -4,9 +4,14 @@ package ch.unisg.tapastasks.tasks.domain; public class NewTaskAddedEvent { public String taskName; public String taskListName; + public String taskId; + public String taskType; - public NewTaskAddedEvent(String taskName, String taskListName) { + public NewTaskAddedEvent(String taskName, String taskListName, String taskId, String taskType) { this.taskName = taskName; this.taskListName = taskListName; + this.taskId = taskId; + this.taskType = taskType; } + } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index 0dcafc3..3decd1f 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -22,13 +22,18 @@ public class Task { private final TaskType taskType; @Getter - private TaskState taskState; + public TaskState taskState; // had to make public for CompleteTaskService + + @Getter + public TaskResult taskResult; // same as above + public Task(TaskName taskName, TaskType taskType) { this.taskName = taskName; this.taskType = taskType; this.taskState = new TaskState(State.OPEN); this.taskId = new TaskId(UUID.randomUUID().toString()); + this.taskResult = new TaskResult(""); } protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { @@ -56,4 +61,9 @@ public class Task { public static class TaskType { private String value; } + + @Value + public static class TaskResult{ + private String value; + } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java index 2b90da5..ccdc59a 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java @@ -20,8 +20,7 @@ public class TaskList { //Note: We do not care about the management of task lists, there is only one within this service //--> using the Singleton pattern here to make lives easy; we will later load it from a repo - //TODO change "tutors" to your group name ("groupx") - private static final TaskList taskList = new TaskList(new TaskListName("tapas-tasks-tutors")); + private static final TaskList taskList = new TaskList(new TaskListName("tapas-tasks-group1")); private TaskList(TaskListName taskListName) { this.taskListName = taskListName; @@ -55,6 +54,17 @@ public class TaskList { return Optional.empty(); } + public Optional deleteTaskById(Task.TaskId id) { + for (Task task: listOfTasks.value){ + if(task.getTaskId().getValue().equalsIgnoreCase(id.getValue())){ + listOfTasks.value.remove(task); + return Optional.of(task); + } + } + + return Optional.empty(); + } + @Value public static class TaskListName { private String value; -- 2.45.1 From dfdad08827629ab751681e5df46e42dfb0d2055c Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Sat, 16 Oct 2021 20:36:28 +0200 Subject: [PATCH 14/94] Implementation of Executor1 --- executor1/.gitignore | 33 ++ .../.mvn/wrapper/MavenWrapperDownloader.java | 117 +++++++ executor1/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + executor1/Dockerfile | 18 + executor1/mvnw | 310 ++++++++++++++++++ executor1/mvnw.cmd | 182 ++++++++++ executor1/pom.xml | 67 ++++ .../unisg/executor1/Executor1Application.java | 16 + .../out/DeleteUserFromRobotAdapter.java | 42 +++ .../out/InstructionToRobotAdapter.java | 46 +++ .../adapter/out/UserToRobotAdapter.java | 46 +++ .../port/out/DeleteUserFromRobotPort.java | 5 + .../port/out/InstructionToRobotPort.java | 5 + .../application/port/out/UserToRobotPort.java | 7 + .../service/TaskAvailableService.java | 28 ++ .../executor1/executor/domain/Executor.java | 42 +++ .../src/main/resources/application.properties | 1 + .../executor2/Executor2ApplicationTests.java | 13 + 19 files changed, 980 insertions(+) create mode 100644 executor1/.gitignore create mode 100644 executor1/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 executor1/.mvn/wrapper/maven-wrapper.jar create mode 100644 executor1/.mvn/wrapper/maven-wrapper.properties create mode 100644 executor1/Dockerfile create mode 100755 executor1/mvnw create mode 100644 executor1/mvnw.cmd create mode 100644 executor1/pom.xml create mode 100644 executor1/src/main/java/ch/unisg/executor1/Executor1Application.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java create mode 100644 executor1/src/main/resources/application.properties create mode 100644 executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java diff --git a/executor1/.gitignore b/executor1/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/executor1/.gitignore @@ -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/ diff --git a/executor1/.mvn/wrapper/MavenWrapperDownloader.java b/executor1/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/executor1/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/executor1/.mvn/wrapper/maven-wrapper.jar b/executor1/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/executor1/.mvn/wrapper/maven-wrapper.properties b/executor1/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/executor1/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/executor1/Dockerfile b/executor1/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/executor1/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] diff --git a/executor1/mvnw b/executor1/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/executor1/mvnw @@ -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 "$@" diff --git a/executor1/mvnw.cmd b/executor1/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/executor1/mvnw.cmd @@ -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% diff --git a/executor1/pom.xml b/executor1/pom.xml new file mode 100644 index 0000000..8a5b9e3 --- /dev/null +++ b/executor1/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + ch.unisg + executor1 + 0.0.1-SNAPSHOT + executor1 + Demo project for Spring Boot + + 11 + scs-asse-fs21-group1 + https://sonarcloud.io + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + ch.unisg + executorBase + 0.0.1-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java new file mode 100644 index 0000000..dfb8d8c --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java @@ -0,0 +1,16 @@ +package ch.unisg.executor1; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import ch.unisg.executor1.executor.domain.Executor; + +@SpringBootApplication +public class Executor1Application { + + public static void main(String[] args) { + SpringApplication.run(Executor1Application.class, args); + Executor.getExecutor(); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java new file mode 100644 index 0000000..94c2309 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java @@ -0,0 +1,42 @@ +package ch.unisg.executor1.executor.adapter.out; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.DeleteUserFromRobotPort; + +@Component +@Primary +public class DeleteUserFromRobotAdapter implements DeleteUserFromRobotPort { + + @Override + public boolean deleteUserFromRobot(String key) { + + String url = "https://api.interactions.ics.unisg.ch/leubot1/v1.3.0/user/" + key; + + var request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .DELETE() + .build(); + + var client = HttpClient.newHttpClient(); + + try { + var response = client.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println(response.statusCode()); + return true; + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return false; + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java new file mode 100644 index 0000000..f8b7012 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java @@ -0,0 +1,46 @@ +package ch.unisg.executor1.executor.adapter.out; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.InstructionToRobotPort; + +@Component +@Primary +public class InstructionToRobotAdapter implements InstructionToRobotPort { + + @Override + public boolean instructionToRobot(String key) { + + String putEndpoint = "https://api.interactions.ics.unisg.ch/leubot1/v1.3.0/elbow"; + + String inputJson = "{ \"value\": 400}"; + var request = HttpRequest.newBuilder() + .uri(URI.create(putEndpoint)) + .header("Content-Type", "application/json") + .header("X-API-KEY", key) + .PUT(HttpRequest.BodyPublishers.ofString(inputJson)) + .build(); + + var client = HttpClient.newHttpClient(); + + try { + var response = client.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println(response.statusCode()); + System.out.println(response.headers()); + return true; + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return false; + + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java new file mode 100644 index 0000000..f874892 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java @@ -0,0 +1,46 @@ +package ch.unisg.executor1.executor.adapter.out; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.UserToRobotPort; + +@Component +@Primary +public class UserToRobotAdapter implements UserToRobotPort { + + @Override + public String userToRobot() { + String postEndpoint = "https://api.interactions.ics.unisg.ch/leubot1/v1.3.0/user"; + + String inputJson = "{ \"name\":\"keanu rahimian\", \"email\":\"keanu.rahimian@student.unisg.ch\"}"; + var request = HttpRequest.newBuilder() + .uri(URI.create(postEndpoint)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(inputJson)) + .build(); + + var client = HttpClient.newHttpClient(); + + try { + var response = client.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println(response.statusCode()); + System.out.println(response.headers()); + String url = response.headers().map().get("location").toString(); + String key = url.split("/")[url.split("/").length-1].split("]")[0]; + return key; + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return null; + + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java new file mode 100644 index 0000000..fc6f5d7 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.out; + +public interface DeleteUserFromRobotPort { + boolean deleteUserFromRobot(String key); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java new file mode 100644 index 0000000..bbf4034 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.out; + +public interface InstructionToRobotPort { + boolean instructionToRobot(String key); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java new file mode 100644 index 0000000..5b011c1 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.application.port.out; + +import ch.unisg.executorBase.executor.domain.ExecutorType; + +public interface UserToRobotPort { + String userToRobot(); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java new file mode 100644 index 0000000..d502053 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -0,0 +1,28 @@ +package ch.unisg.executor1.executor.application.service; + +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.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(); + } + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java new file mode 100644 index 0000000..4a7734c --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -0,0 +1,42 @@ +package ch.unisg.executor1.executor.domain; + +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.concurrent.TimeUnit; + +import ch.unisg.executor1.executor.adapter.out.DeleteUserFromRobotAdapter; +import ch.unisg.executor1.executor.adapter.out.InstructionToRobotAdapter; +import ch.unisg.executor1.executor.adapter.out.UserToRobotAdapter; +import ch.unisg.executor1.executor.application.port.out.DeleteUserFromRobotPort; +import ch.unisg.executor1.executor.application.port.out.InstructionToRobotPort; +import ch.unisg.executor1.executor.application.port.out.UserToRobotPort; +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.ROBOT); + private final UserToRobotPort userToRobotPort = new UserToRobotAdapter(); + private final InstructionToRobotPort instructionToRobotPort = new InstructionToRobotAdapter(); + private final DeleteUserFromRobotPort deleteUserFromRobotPort = new DeleteUserFromRobotAdapter(); + + public static Executor getExecutor() { + return executor; + } + + private Executor(ExecutorType executorType) { + super(executorType); + } + + @Override + protected + String execution() { + + String key = userToRobotPort.userToRobot(); + boolean result1 = instructionToRobotPort.instructionToRobot(key); + deleteUserFromRobotPort.deleteUserFromRobot(key); + + return Boolean.toString(result1); + } + +} \ No newline at end of file diff --git a/executor1/src/main/resources/application.properties b/executor1/src/main/resources/application.properties new file mode 100644 index 0000000..4d360de --- /dev/null +++ b/executor1/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8081 diff --git a/executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java b/executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java new file mode 100644 index 0000000..5724a1c --- /dev/null +++ b/executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.executor2; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Executor2ApplicationTests { + + @Test + void contextLoads() { + } + +} -- 2.45.1 From 89a49ba786fb42ac8911936f81982febd24848d2 Mon Sep 17 00:00:00 2001 From: jj187 Date: Sat, 16 Oct 2021 20:54:54 +0200 Subject: [PATCH 15/94] Added latest changes --- .../tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java index 6758337..af721d1 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java @@ -32,6 +32,7 @@ public class DeleteTaskWebController { // Check if the task with the given identifier exists if (deleteATask.isEmpty()) { + // If not, through a 404 Not Found status code throw new ResponseStatusException(HttpStatus.NOT_FOUND); } -- 2.45.1 From 94e5742ad252bc11d87c6c8d8da1d961cff48e9e Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 17 Oct 2021 00:17:13 +0200 Subject: [PATCH 16/94] Implemented all issues regarding Executor Pool. Did not complete the implementation of duplicate Ip/port check on adding a new executor --- executor-pool/pom.xml | 22 +++++- ...ewExecutorToExecutorPoolWebController.java | 40 ++++++++++ .../adapter/in/web/ExecutorMediaType.java | 33 +++++++++ ...utorInExecutorPoolByTypeWebController.java | 35 +++++++++ ...lExecutorsInExecutorPoolWebController.java | 32 ++++++++ ...ExecutorFromExecutorPoolWebController.java | 39 ++++++++++ .../AddNewExecutorToExecutorPoolCommand.java | 28 +++++++ .../AddNewExecutorToExecutorPoolUseCase.java | 7 ++ ...tAllExecutorInExecutorPoolByTypeQuery.java | 18 +++++ ...llExecutorInExecutorPoolByTypeUseCase.java | 9 +++ .../GetAllExecutorsInExecutorPoolUseCase.java | 10 +++ ...RemoveExecutorFromExecutorPoolCommand.java | 24 ++++++ ...RemoveExecutorFromExecutorPoolUseCase.java | 9 +++ .../AddNewExecutorToExecutorPoolService.java | 25 +++++++ ...llExecutorInExecutorPoolByTypeService.java | 24 ++++++ .../GetAllExecutorsInExecutorPoolService.java | 22 ++++++ ...RemoveExecutorFromExecutorPoolService.java | 22 ++++++ .../executorpool/domain/ExecutorClass.java | 42 +++++++++++ .../executorpool/domain/ExecutorPool.java | 74 +++++++++++++++++++ .../service/AddNewTaskToTaskListService.java | 1 + 20 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolUseCase.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolUseCase.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolUseCase.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolService.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml index dea007e..59d5b2a 100644 --- a/executor-pool/pom.xml +++ b/executor-pool/pom.xml @@ -45,7 +45,27 @@ json 20210307 - + + javax.validation + validation-api + compile + + + jakarta.validation + jakarta.validation-api + + + ch.unisg + tapas-tasks + 0.0.1-SNAPSHOT + compile + + + javax.transaction + javax.transaction-api + compile + + diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java new file mode 100644 index 0000000..7967b6b --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java @@ -0,0 +1,40 @@ +package ch.unisg.executorpool.adapter.in.web; + +import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolUseCase; +import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolCommand; +import ch.unisg.executorpool.domain.ExecutorClass; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; +import javax.validation.ConstraintViolationException; + +@RestController +public class AddNewExecutorToExecutorPoolWebController { + private final AddNewExecutorToExecutorPoolUseCase addNewExecutorToExecutorPoolUseCase; + + public AddNewExecutorToExecutorPoolWebController(AddNewExecutorToExecutorPoolUseCase addNewExecutorToExecutorPoolUseCase){ + this.addNewExecutorToExecutorPoolUseCase = addNewExecutorToExecutorPoolUseCase; + } + + @PostMapping(path = "/executor-pool/AddExecutor", consumes = {ExecutorMediaType.EXECUTOR_MEDIA_TYPE}) + public ResponseEntity addNewExecutorToExecutorPool(@RequestBody ExecutorClass executorClass){ + try{ + AddNewExecutorToExecutorPoolCommand command = new AddNewExecutorToExecutorPoolCommand( + executorClass.getExecutorIp(), executorClass.getExecutorPort(), executorClass.getExecutorTaskType() + ); + + ExecutorClass newExecutor = addNewExecutorToExecutorPoolUseCase.addNewExecutorToExecutorPool(command); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + + return new ResponseEntity<>(ExecutorMediaType.serialize(newExecutor), responseHeaders, HttpStatus.CREATED); + } catch (ConstraintViolationException e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java new file mode 100644 index 0000000..cbbc33b --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java @@ -0,0 +1,33 @@ +package ch.unisg.executorpool.adapter.in.web; + +import ch.unisg.executorpool.domain.ExecutorClass; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.List; + +final public class ExecutorMediaType { + public static final String EXECUTOR_MEDIA_TYPE = "application/json"; + + public static String serialize(ExecutorClass executorClass) { + JSONObject payload = new JSONObject(); + + payload.put("executorIp", executorClass.getExecutorIp().getValue()); + payload.put("executorPort", executorClass.getExecutorPort().getValue()); + payload.put("executorTaskType", executorClass.getExecutorTaskType().getValue()); + + return payload.toString(); + } + + public static String serialize(List listOfExecutors) { + String serializedList = "[ \n"; + + for (ExecutorClass executor: listOfExecutors) { + serializedList += serialize(executor) + ",\n"; + } + + return serializedList + "\n ]"; + } + + private ExecutorMediaType() { } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java new file mode 100644 index 0000000..2595781 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java @@ -0,0 +1,35 @@ +package ch.unisg.executorpool.adapter.in.web; + +import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeQuery; +import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; +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 java.util.List; + +@RestController +public class GetAllExecutorInExecutorPoolByTypeWebController { + private final GetAllExecutorInExecutorPoolByTypeUseCase getAllExecutorInExecutorPoolByTypeUseCase; + + public GetAllExecutorInExecutorPoolByTypeWebController(GetAllExecutorInExecutorPoolByTypeUseCase getAllExecutorInExecutorPoolByTypeUseCase){ + this.getAllExecutorInExecutorPoolByTypeUseCase = getAllExecutorInExecutorPoolByTypeUseCase; + } + + @GetMapping(path = "/executor-pool/GetAllExecutorInExecutorPoolByType/{taskType}") + public ResponseEntity getAllExecutorInExecutorPoolByType(@PathVariable("taskType") String taskType){ + GetAllExecutorInExecutorPoolByTypeQuery query = new GetAllExecutorInExecutorPoolByTypeQuery(new ExecutorTaskType(taskType)); + List matchedExecutors = getAllExecutorInExecutorPoolByTypeUseCase.getAllExecutorInExecutorPoolByType(query); + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + + return new ResponseEntity<>(ExecutorMediaType.serialize(matchedExecutors), responseHeaders, HttpStatus.OK); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java new file mode 100644 index 0000000..ada219c --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java @@ -0,0 +1,32 @@ +package ch.unisg.executorpool.adapter.in.web; + +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.tapastasks.tasks.adapter.in.web.TaskMediaType; +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.RestController; + +import java.util.List; + +@RestController +public class GetAllExecutorsInExecutorPoolWebController { + private final GetAllExecutorsInExecutorPoolUseCase getAllExecutorsInExecutorPoolUseCase; + + public GetAllExecutorsInExecutorPoolWebController(GetAllExecutorsInExecutorPoolUseCase getAllExecutorsInExecutorPoolUseCase){ + this.getAllExecutorsInExecutorPoolUseCase = getAllExecutorsInExecutorPoolUseCase; + } + + @GetMapping(path = "executor-pool/GetAllExecutorsinExecutorPool") + public ResponseEntity getAllExecutorsInExecutorPool(){ + List executorClassList = getAllExecutorsInExecutorPoolUseCase.getAllExecutorsInExecutorPool(); + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + + return new ResponseEntity<>(ExecutorMediaType.serialize(executorClassList), responseHeaders, HttpStatus.OK); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java new file mode 100644 index 0000000..69bbde3 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java @@ -0,0 +1,39 @@ +package ch.unisg.executorpool.adapter.in.web; + +import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolCommand; +import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Optional; + +@RestController +public class RemoveExecutorFromExecutorPoolWebController { + private final RemoveExecutorFromExecutorPoolUseCase removeExecutorFromExecutorPoolUseCase; + + public RemoveExecutorFromExecutorPoolWebController(RemoveExecutorFromExecutorPoolUseCase removeExecutorFromExecutorPoolUseCase){ + this.removeExecutorFromExecutorPoolUseCase = removeExecutorFromExecutorPoolUseCase; + } + + @PostMapping(path = "/executor-pool/RemoveExecutor", consumes = {ExecutorMediaType.EXECUTOR_MEDIA_TYPE}) + public ResponseEntity removeExecutorFromExecutorPool(@RequestBody ExecutorClass executorClass){ + RemoveExecutorFromExecutorPoolCommand command = new RemoveExecutorFromExecutorPoolCommand(executorClass.getExecutorIp(), executorClass.getExecutorPort()); + Optional removedExecutor = removeExecutorFromExecutorPoolUseCase.removeExecutorFromExecutorPool(command); + + if(removedExecutor.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + + return new ResponseEntity<>(ExecutorMediaType.serialize(removedExecutor.get()), responseHeaders, + HttpStatus.OK); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java new file mode 100644 index 0000000..26b495e --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java @@ -0,0 +1,28 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorPool; +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; +import lombok.Value; +import javax.validation.constraints.NotNull; + +@Value +public class AddNewExecutorToExecutorPoolCommand extends SelfValidating { + @NotNull + private final ExecutorIp executorIp; + + @NotNull + private final ExecutorPort executorPort; + + @NotNull + private final ExecutorTaskType executorTaskType; + + public AddNewExecutorToExecutorPoolCommand(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ + this.executorIp = executorIp; + this.executorPort = executorPort; + this.executorTaskType = executorTaskType; + this.validateSelf(); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolUseCase.java new file mode 100644 index 0000000..51b28ba --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolUseCase.java @@ -0,0 +1,7 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; + +public interface AddNewExecutorToExecutorPoolUseCase { + ExecutorClass addNewExecutorToExecutorPool(AddNewExecutorToExecutorPoolCommand command); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java new file mode 100644 index 0000000..509dba5 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java @@ -0,0 +1,18 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; +import ch.unisg.tapastasks.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +public class GetAllExecutorInExecutorPoolByTypeQuery extends SelfValidating { + @NotNull + private final ExecutorTaskType executorTaskType; + + public GetAllExecutorInExecutorPoolByTypeQuery(ExecutorTaskType executorTaskType){ + this.executorTaskType = executorTaskType; + this.validateSelf(); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java new file mode 100644 index 0000000..9f612bf --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java @@ -0,0 +1,9 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; + +import java.util.List; + +public interface GetAllExecutorInExecutorPoolByTypeUseCase { + List getAllExecutorInExecutorPoolByType(GetAllExecutorInExecutorPoolByTypeQuery query); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolUseCase.java new file mode 100644 index 0000000..b7f2eb7 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolUseCase.java @@ -0,0 +1,10 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorPool; + +import java.util.List; + +public interface GetAllExecutorsInExecutorPoolUseCase { + List getAllExecutorsInExecutorPool(); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java new file mode 100644 index 0000000..a35b9ed --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java @@ -0,0 +1,24 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +public class RemoveExecutorFromExecutorPoolCommand extends SelfValidating { + @NotNull + private final ExecutorIp executorIp; + + @NotNull + private final ExecutorPort executorPort; + + public RemoveExecutorFromExecutorPoolCommand(ExecutorIp executorIp, ExecutorPort executorPort){ + this.executorIp = executorIp; + this.executorPort = executorPort; + this.validateSelf(); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolUseCase.java new file mode 100644 index 0000000..8b899e7 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolUseCase.java @@ -0,0 +1,9 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; + +import java.util.Optional; + +public interface RemoveExecutorFromExecutorPoolUseCase { + Optional removeExecutorFromExecutorPool(RemoveExecutorFromExecutorPoolCommand command); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java new file mode 100644 index 0000000..e1ef237 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java @@ -0,0 +1,25 @@ +package ch.unisg.executorpool.application.service; + +import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolUseCase; +import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolCommand; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorPool; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.yaml.snakeyaml.constructor.DuplicateKeyException; + +import javax.transaction.Transactional; +import javax.validation.ConstraintViolationException; + +@RequiredArgsConstructor +@Component +@Transactional +public class AddNewExecutorToExecutorPoolService implements AddNewExecutorToExecutorPoolUseCase { + + @Override + public ExecutorClass addNewExecutorToExecutorPool(AddNewExecutorToExecutorPoolCommand command){ + ExecutorPool executorPool = ExecutorPool.getExecutorPool(); + + return executorPool.addNewExecutor(command.getExecutorIp(), command.getExecutorPort(), command.getExecutorTaskType()); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java new file mode 100644 index 0000000..74988b2 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java @@ -0,0 +1,24 @@ +package ch.unisg.executorpool.application.service; + +import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeQuery; +import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorPool; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.List; + +@RequiredArgsConstructor +@Component +@Transactional +public class GetAllExecutorInExecutorPoolByTypeService implements GetAllExecutorInExecutorPoolByTypeUseCase { + + @Override + public List getAllExecutorInExecutorPoolByType(GetAllExecutorInExecutorPoolByTypeQuery query){ + ExecutorPool executorPool = ExecutorPool.getExecutorPool(); + return executorPool.getAllExecutorsByType(query.getExecutorTaskType()); + } + +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolService.java new file mode 100644 index 0000000..589cf46 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolService.java @@ -0,0 +1,22 @@ +package ch.unisg.executorpool.application.service; + +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorPool; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.List; + +@RequiredArgsConstructor +@Component +@Transactional +public class GetAllExecutorsInExecutorPoolService implements GetAllExecutorsInExecutorPoolUseCase { + + @Override + public List getAllExecutorsInExecutorPool(){ + ExecutorPool executorPool = ExecutorPool.getExecutorPool(); + return executorPool.getListOfExecutors().getValue(); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java new file mode 100644 index 0000000..639ba7f --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java @@ -0,0 +1,22 @@ +package ch.unisg.executorpool.application.service; + +import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolCommand; +import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolUseCase; +import ch.unisg.executorpool.domain.ExecutorClass; +import ch.unisg.executorpool.domain.ExecutorPool; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.Optional; + +@RequiredArgsConstructor +@Component +@Transactional +public class RemoveExecutorFromExecutorPoolService implements RemoveExecutorFromExecutorPoolUseCase { + @Override + public Optional removeExecutorFromExecutorPool(RemoveExecutorFromExecutorPoolCommand command){ + ExecutorPool executorPool = ExecutorPool.getExecutorPool(); + return executorPool.removeExecutorByIpAndPort(command.getExecutorIp(), command.getExecutorPort()); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java new file mode 100644 index 0000000..d1fca00 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java @@ -0,0 +1,42 @@ +package ch.unisg.executorpool.domain; + +import lombok.Getter; +import lombok.Value; + +public class ExecutorClass { + + @Getter + private final ExecutorIp executorIp; + + @Getter + private final ExecutorPort executorPort; + + @Getter + private final ExecutorTaskType executorTaskType; + + public ExecutorClass(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ + this.executorIp = executorIp; + this.executorPort = executorPort; + this.executorTaskType = executorTaskType; + } + + protected static ExecutorClass createExecutorClass(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ + System.out.println("New Task: " + executorIp.getValue() + " " + executorPort.getValue() + " " + executorTaskType.getValue()); + return new ExecutorClass(executorIp, executorPort, executorTaskType); + } + + @Value + public static class ExecutorIp { + private String value; + } + + @Value + public static class ExecutorPort { + private String value; + } + + @Value + public static class ExecutorTaskType { + private String value; + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java new file mode 100644 index 0000000..dd5375b --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java @@ -0,0 +1,74 @@ +package ch.unisg.executorpool.domain; + +import lombok.Getter; +import lombok.Value; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +public class ExecutorPool { + + @Getter + private final ListOfExecutors listOfExecutors; + + private static final ExecutorPool executorPool = new ExecutorPool(); + + private ExecutorPool(){ + this.listOfExecutors = new ListOfExecutors(new LinkedList()); + } + + public static ExecutorPool getExecutorPool() { return executorPool; } + + public ExecutorClass addNewExecutor(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort, ExecutorClass.ExecutorTaskType executorTaskType){ + ExecutorClass newExecutor = ExecutorClass.createExecutorClass(executorIp, executorPort, executorTaskType); + listOfExecutors.value.add(newExecutor); + System.out.println("Number of executors: " + listOfExecutors.value.size()); + return newExecutor; + } + + public Optional getExecutorByIpAndPort(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort){ + + for (ExecutorClass executor : listOfExecutors.value ) { + // TODO can this be simplified by overwriting equals()? + if(executor.getExecutorIp().getValue().equalsIgnoreCase(executorIp.getValue()) && + executor.getExecutorPort().getValue().equalsIgnoreCase(executorPort.getValue())){ + return Optional.of(executor); + } + } + + return Optional.empty(); + } + + public List getAllExecutorsByType(ExecutorClass.ExecutorTaskType executorTaskType){ + + List matchedExecutors = new LinkedList(); + + for (ExecutorClass executor : listOfExecutors.value ) { + // TODO can this be simplified by overwriting equals()? + if(executor.getExecutorTaskType().getValue().equalsIgnoreCase(executorTaskType.getValue())){ + matchedExecutors.add(executor); + } + } + + return matchedExecutors; + } + + public Optional removeExecutorByIpAndPort(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort){ + for (ExecutorClass executor : listOfExecutors.value ) { + // TODO can this be simplified by overwriting equals()? + if(executor.getExecutorIp().getValue().equalsIgnoreCase(executorIp.getValue()) && + executor.getExecutorPort().getValue().equalsIgnoreCase(executorPort.getValue())){ + listOfExecutors.value.remove(executor); + return Optional.of(executor); + } + } + + return Optional.empty(); + } + + @Value + public static class ListOfExecutors { + private List value; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 48c75a6..7e3320e 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -31,6 +31,7 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase if (newTask != null) { NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent(newTask.getTaskName().getValue(), taskList.getTaskListName().getValue()); + newTaskAddedEventPort.publishNewTaskAddedEvent(newTaskAdded); } -- 2.45.1 From a61f1118794b8526ed1a93dec9f046b21c95844a Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 00:31:48 +0200 Subject: [PATCH 17/94] assignment service + executor pool improvments --- assignment/pom.xml | 23 ++++ .../ch/unisg/assignment/TestController.java | 12 -- .../in/web/ApplyForTaskController.java | 36 ++++++ .../adapter/in/web/NewTaskController.java | 41 ++++++ .../in/web/TaskCompletedController.java | 34 +++++ .../out/web/PublishNewTaskEventAdapter.java | 40 ++++++ .../web/PublishTaskAssignedEventAdapter.java | 46 +++++++ .../web/PublishTaskCompletedEventAdapter.java | 49 +++++++ .../port/in/ApplyForTaskCommand.java | 29 +++++ .../port/in/ApplyForTaskUseCase.java | 7 + .../application/port/in/NewTaskCommand.java | 23 ++++ .../application/port/in/NewTaskUseCase.java | 5 + .../port/in/TaskCompletedCommand.java | 33 +++++ .../port/in/TaskCompletedUseCase.java | 5 + .../port/out/NewTaskEventPort.java | 7 + .../port/out/TaskAssignedEventPort.java | 7 + .../port/out/TaskCompletedEventPort.java | 7 + .../service/ApplyForTaskService.java | 34 +++++ .../application/service/NewTaskService.java | 46 +++++++ .../service/TaskCompletedService.java | 31 +++++ .../assignment/domain/ExecutorInfo.java | 18 +++ .../assignment/domain/NewTaskEvent.java | 9 ++ .../assignment/assignment/domain/Roster.java | 49 +++++++ .../assignment/domain/RosterItem.java | 27 ++++ .../assignment/assignment/domain/Task.java | 27 ++++ .../assignment/domain/TaskAssignedEvent.java | 9 ++ .../assignment/domain/TaskCompletedEvent.java | 15 +++ .../assignment/common/SelfValidating.java | 31 +++++ .../src/main/resources/application.properties | 2 +- docker-compose.yaml | 120 +++++++++--------- executor-base/pom.xml | 6 + .../executorBase/common/SelfValidating.java | 1 + .../in/web/TaskAvailableController.java | 2 +- .../web/ExecutionFinishedEventAdapter.java | 38 +++--- .../adapter/out/web/GetAssignmentAdapter.java | 39 ++++-- .../port/in/TaskAvailableCommand.java | 2 +- .../port/out/GetAssignmentPort.java | 2 +- .../executor/domain/ExecutorBase.java | 24 +--- .../executorBase/executor/domain/Task.java | 3 +- .../in/web/TaskAvailableController.java | 36 ++++++ .../service/TaskAvailableService.java | 2 +- .../executor2/executor/domain/Executor.java | 4 +- .../src/main/resources/application.properties | 2 +- 43 files changed, 849 insertions(+), 134 deletions(-) delete mode 100644 assignment/src/main/java/ch/unisg/assignment/TestController.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java create mode 100644 executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java diff --git a/assignment/pom.xml b/assignment/pom.xml index 43af6c3..99996b8 100644 --- a/assignment/pom.xml +++ b/assignment/pom.xml @@ -30,6 +30,11 @@ runtime true + + + org.springframework.boot + spring-boot-starter-validation + org.projectlombok lombok @@ -40,6 +45,24 @@ spring-boot-starter-test test + + javax.validation + validation-api + 1.1.0.Final + + + javax.transaction + javax.transaction-api + 1.2 + + + + org.json + json + 20210307 + + + diff --git a/assignment/src/main/java/ch/unisg/assignment/TestController.java b/assignment/src/main/java/ch/unisg/assignment/TestController.java deleted file mode 100644 index ac8e4f9..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/TestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unisg.assignment; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TestController { - @RequestMapping("/") - public String index() { - return "Hello World! Assignment"; - } -} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java new file mode 100644 index 0000000..e83e74d --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java @@ -0,0 +1,36 @@ +package ch.unisg.assignment.assignment.adapter.in.web; + +import javax.validation.ConstraintViolationException; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; +import ch.unisg.assignment.assignment.domain.ExecutorInfo; +import ch.unisg.assignment.assignment.domain.Task; + +@RestController +public class ApplyForTaskController { + private final ApplyForTaskUseCase applyForTaskUseCase; + + public ApplyForTaskController(ApplyForTaskUseCase applyForTaskUseCase) { + this.applyForTaskUseCase = applyForTaskUseCase; + } + + @PostMapping(path = "/task/apply", consumes = {"application/json"}) + public Task applyForTask(@RequestBody ExecutorInfo executorInfo) { + try { + ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(), + executorInfo.getIp(), executorInfo.getPort()); + + return applyForTaskUseCase.applyForTask(command); + + } catch (ConstraintViolationException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java new file mode 100644 index 0000000..d95fdd5 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java @@ -0,0 +1,41 @@ +package ch.unisg.assignment.assignment.adapter.in.web; + +import javax.validation.ConstraintViolationException; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; +import ch.unisg.assignment.assignment.domain.Task; + +@RestController +public class NewTaskController { + private final NewTaskUseCase newTaskUseCase; + + public NewTaskController(NewTaskUseCase newTaskUseCase) { + this.newTaskUseCase = newTaskUseCase; + } + + @PostMapping(path = "/task", consumes = {"application/json"}) + public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { + try { + NewTaskCommand command = new NewTaskCommand( + task.getTaskID(), task.getTaskType() + ); + + boolean success = newTaskUseCase.addNewTaskToQueue(command); + + if (success) { + return new ResponseEntity<>(HttpStatus.CREATED); + } + return new ResponseEntity<>(HttpStatus.CONFLICT); + } catch (ConstraintViolationException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java new file mode 100644 index 0000000..e8335ed --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java @@ -0,0 +1,34 @@ +package ch.unisg.assignment.assignment.adapter.in.web; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand; +import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase; +import ch.unisg.assignment.assignment.domain.Task; + +@RestController +public class TaskCompletedController { + + private final TaskCompletedUseCase taskCompletedUseCase; + + public TaskCompletedController(TaskCompletedUseCase taskCompletedUseCase) { + this.taskCompletedUseCase = taskCompletedUseCase; + } + + @PostMapping(path = "/task/completed", consumes = {"application/json"}) + public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { + + TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), task.getTaskType(), + task.getStatus(), task.getResult()); + + taskCompletedUseCase.taskCompleted(command); + + return new ResponseEntity<>(HttpStatus.OK); + + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java new file mode 100644 index 0000000..c08651b --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -0,0 +1,40 @@ +package ch.unisg.assignment.assignment.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 org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; +import ch.unisg.assignment.assignment.domain.NewTaskEvent; + +@Component +@Primary +public class PublishNewTaskEventAdapter implements NewTaskEventPort { + + String server = "http://127.0.0.1:8085"; + + @Override + public void publishNewTaskEvent(NewTaskEvent event) { + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server + "/newtask/" + event.taskType)) + .GET() + .build(); + + + try { + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java new file mode 100644 index 0000000..7f38d60 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -0,0 +1,46 @@ +package ch.unisg.assignment.assignment.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 org.json.JSONObject; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; +import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; + +@Component +@Primary +public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { + + String server = "http://127.0.0.1:8085"; + + @Override + public void publishTaskAssignedEvent(TaskAssignedEvent event) { + + String body = new JSONObject() + .put("taskId", event.taskID) + .toString(); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server + "/tasks/completeTask")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + + try { + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java new file mode 100644 index 0000000..d63d710 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -0,0 +1,49 @@ +package ch.unisg.assignment.assignment.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 org.json.JSONObject; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; +import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; + +@Component +@Primary +public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort { + + String server = "http://127.0.0.1:8081"; + + @Override + public void publishTaskCompleted(TaskCompletedEvent event) { + + String body = new JSONObject() + .put("taskId", event.taskID) + .put("status", event.status) + .put("taskResult", event.result) + .toString(); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server + "/tasks/completeTask")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + + try { + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java new file mode 100644 index 0000000..76d0e41 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java @@ -0,0 +1,29 @@ +package ch.unisg.assignment.assignment.application.port.in; + +import javax.validation.constraints.NotNull; + +import ch.unisg.assignment.common.SelfValidating; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class ApplyForTaskCommand extends SelfValidating{ + + @NotNull + private final String taskType; + + @NotNull + private final String executorIP; + + + @NotNull + private final int executorPort; + + public ApplyForTaskCommand(String taskType, String executorIP, int executorPort) { + this.taskType = taskType; + this.executorIP = executorIP; + this.executorPort = executorPort; + this.validateSelf(); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java new file mode 100644 index 0000000..1e7180a --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.assignment.application.port.in; + +import ch.unisg.assignment.assignment.domain.Task; + +public interface ApplyForTaskUseCase { + Task applyForTask(ApplyForTaskCommand applyForTaskCommand); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java new file mode 100644 index 0000000..98f4579 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java @@ -0,0 +1,23 @@ +package ch.unisg.assignment.assignment.application.port.in; + +import javax.validation.constraints.NotNull; + +import ch.unisg.assignment.common.SelfValidating; + +import lombok.Value; + +@Value +public class NewTaskCommand extends SelfValidating { + + @NotNull + private final String taskID; + + @NotNull + private final String taskType; + + public NewTaskCommand(String taskID, String taskType) { + this.taskID = taskID; + this.taskType = taskType; + this.validateSelf(); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java new file mode 100644 index 0000000..21f084e --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java @@ -0,0 +1,5 @@ +package ch.unisg.assignment.assignment.application.port.in; + +public interface NewTaskUseCase { + boolean addNewTaskToQueue(NewTaskCommand newTaskCommand); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java new file mode 100644 index 0000000..474456d --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java @@ -0,0 +1,33 @@ +package ch.unisg.assignment.assignment.application.port.in; + +import javax.validation.constraints.NotNull; + +import ch.unisg.assignment.common.SelfValidating; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class TaskCompletedCommand extends SelfValidating{ + + @NotNull + private final String taskID; + + @NotNull + private final String taskType; + + @NotNull + private final String taskStatus; + + @NotNull + private final String taskResult; + + public TaskCompletedCommand(String taskID, String taskType, String taskStatus, String taskResult) { + this.taskID = taskID; + this.taskType = taskType; + this.taskStatus = taskStatus; + this.taskResult = taskResult; + this.validateSelf(); + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java new file mode 100644 index 0000000..1902952 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java @@ -0,0 +1,5 @@ +package ch.unisg.assignment.assignment.application.port.in; + +public interface TaskCompletedUseCase { + void taskCompleted(TaskCompletedCommand taskCompletedCommand); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java new file mode 100644 index 0000000..521af1a --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.assignment.application.port.out; + +import ch.unisg.assignment.assignment.domain.NewTaskEvent; + +public interface NewTaskEventPort { + void publishNewTaskEvent(NewTaskEvent event); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java new file mode 100644 index 0000000..0cd1cae --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.assignment.application.port.out; + +import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; + +public interface TaskAssignedEventPort { + void publishTaskAssignedEvent(TaskAssignedEvent taskAssignedEvent); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java new file mode 100644 index 0000000..37b4ff3 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.assignment.application.port.out; + +import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; + +public interface TaskCompletedEventPort { + void publishTaskCompleted(TaskCompletedEvent event); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java new file mode 100644 index 0000000..dac9a70 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java @@ -0,0 +1,34 @@ +package ch.unisg.assignment.assignment.application.service; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; +import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; +import ch.unisg.assignment.assignment.domain.Roster; +import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +@Transactional +public class ApplyForTaskService implements ApplyForTaskUseCase { + + private final TaskAssignedEventPort taskAssignedEventPort; + + @Override + public Task applyForTask(ApplyForTaskCommand command) { + Task task = Roster.getInstance().assignTaskToExecutor(command.getTaskType(), + command.getExecutorIP(), command.getExecutorPort()); + + if (task != null) { + taskAssignedEventPort.publishTaskAssignedEvent(new TaskAssignedEvent(task.getTaskID())); + } + + return task; + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java new file mode 100644 index 0000000..b662cd2 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java @@ -0,0 +1,46 @@ +package ch.unisg.assignment.assignment.application.service; + +import java.util.Arrays; +import java.util.List; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; +import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; +import ch.unisg.assignment.assignment.domain.NewTaskEvent; +import ch.unisg.assignment.assignment.domain.Roster; +import ch.unisg.assignment.assignment.domain.Task; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +@Transactional +public class NewTaskService implements NewTaskUseCase { + + private final NewTaskEventPort newTaskEventPort; + + @Override + public boolean addNewTaskToQueue(NewTaskCommand command) { + + // TODO Get availableTaskTypes from executor pool + List availableTaskTypes = Arrays.asList("addition", "robot"); + + if (!availableTaskTypes.contains(command.getTaskType())) { + return false; + } + + Task task = new Task(command.getTaskID(), command.getTaskType()); + + Roster.getInstance().addTaskToQueue(task); + + // TODO this event should be in the roster function xyz + NewTaskEvent newTaskEvent = new NewTaskEvent(task.getTaskType()); + newTaskEventPort.publishNewTaskEvent(newTaskEvent); + + return true; + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java new file mode 100644 index 0000000..1295336 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java @@ -0,0 +1,31 @@ +package ch.unisg.assignment.assignment.application.service; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand; +import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase; +import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; +import ch.unisg.assignment.assignment.domain.Roster; +import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +@Transactional +public class TaskCompletedService implements TaskCompletedUseCase { + + private final TaskCompletedEventPort taskCompletedEventPort; + + @Override + public void taskCompleted(TaskCompletedCommand command) { + + Roster.getInstance().taskCompleted(command.getTaskID()); + + taskCompletedEventPort.publishTaskCompleted(new TaskCompletedEvent(command.getTaskID(), + command.getTaskStatus(), command.getTaskResult())); + + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java new file mode 100644 index 0000000..3b7432c --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java @@ -0,0 +1,18 @@ +package ch.unisg.assignment.assignment.domain; + +import lombok.Getter; +import lombok.Setter; + +public class ExecutorInfo { + @Getter + @Setter + private String ip; + + @Getter + @Setter + private int port; + + @Getter + @Setter + private String executorType; +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java new file mode 100644 index 0000000..57084ee --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java @@ -0,0 +1,9 @@ +package ch.unisg.assignment.assignment.domain; + +public class NewTaskEvent { + public String taskType; + + public NewTaskEvent(String taskType) { + this.taskType = taskType; + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java new file mode 100644 index 0000000..054c094 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java @@ -0,0 +1,49 @@ +package ch.unisg.assignment.assignment.domain; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class Roster { + + private static final Roster roster = new Roster(); + + private HashMap> queues = new HashMap<>(); + + private HashMap rosterMap = new HashMap<>(); + + public static Roster getInstance() { + return roster; + } + + private Roster() {} + + public void addTaskToQueue(Task task) { + if (queues.containsKey(task.getTaskType().toUpperCase())) { + queues.get(task.getTaskType().toUpperCase()).add(task); + } else { + queues.put(task.getTaskType().toUpperCase(), new ArrayList<>(Arrays.asList(task))); + } + } + + public Task assignTaskToExecutor(String taskType, String executorIP, int executorPort) { + if (!queues.containsKey(taskType.toUpperCase())) { + return null; + } + if (queues.get(taskType.toUpperCase()).isEmpty()) { + return null; + } + + Task task = queues.get(taskType.toUpperCase()).remove(0); + + rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(), task.getTaskType(), + executorIP, executorPort)); + + return task; + } + + public void taskCompleted(String taskID) { + rosterMap.remove(taskID); + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java new file mode 100644 index 0000000..6e050d4 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java @@ -0,0 +1,27 @@ +package ch.unisg.assignment.assignment.domain; + +import lombok.Getter; + +public class RosterItem { + + @Getter + private String taskID; + + @Getter + private String taskType; + + @Getter + private String executorIP; + + @Getter + private int executorPort; + + + public RosterItem(String taskID, String taskType, String executorIP, int executorPort) { + this.taskID = taskID; + this.taskType = taskType; + this.executorIP = executorIP; + this.executorPort = executorPort; + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java new file mode 100644 index 0000000..6db5111 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java @@ -0,0 +1,27 @@ +package ch.unisg.assignment.assignment.domain; + +import lombok.Getter; +import lombok.Setter; + +public class Task { + + @Getter + private String taskID; + + @Getter + private String taskType; + + @Getter + @Setter + private String result; + + @Getter + @Setter + private String status; + + public Task(String taskID, String taskType) { + this.taskID = taskID; + this.taskType = taskType; + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java new file mode 100644 index 0000000..8acd144 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java @@ -0,0 +1,9 @@ +package ch.unisg.assignment.assignment.domain; + +public class TaskAssignedEvent { + public String taskID; + + public TaskAssignedEvent(String taskID) { + this.taskID = taskID; + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java new file mode 100644 index 0000000..89f26ae --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java @@ -0,0 +1,15 @@ +package ch.unisg.assignment.assignment.domain; + +public class TaskCompletedEvent { + public String taskID; + + public String status; + + public String result; + + public TaskCompletedEvent(String taskID, String status, String result) { + this.taskID = taskID; + this.status = status; + this.result = result; + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java b/assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java new file mode 100644 index 0000000..a8d366f --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java @@ -0,0 +1,31 @@ +package ch.unisg.assignment.common; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import java.util.Set; + +public abstract class SelfValidating { + + private Validator validator; + + protected SelfValidating() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Evaluates all Bean Validations on the attributes of this + * instance. + */ + protected void validateSelf() { + @SuppressWarnings("unchecked") + Set> violations = validator.validate((T) this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} + diff --git a/assignment/src/main/resources/application.properties b/assignment/src/main/resources/application.properties index 4d360de..3cf12af 100644 --- a/assignment/src/main/resources/application.properties +++ b/assignment/src/main/resources/application.properties @@ -1 +1 @@ -server.port=8081 +server.port=8082 diff --git a/docker-compose.yaml b/docker-compose.yaml index 76b8af1..8c4bfcf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,62 +1,62 @@ version: "3.6" services: - tapas-tasks: - container_name: tapas-tasks - build: - context: "./tapas-tasks" - dockerfile: "Dockerfile" - target: development - ports: - - "8081:8081" - - "5005:5005" - volumes: - - ./tapas-tasks/src:/opt/app/src - - ./tapas-tasks/target:/opt/app/target - assignment: - container_name: assignment - build: - context: "./assignment" - dockerfile: "Dockerfile" - target: development - ports: - - "8082:8081" - - "5006:5005" - volumes: - - ./assignment/src:/opt/app/src - - ./assignment/target:/opt/app/target - executor-pool: - container_name: executor-pool - build: - context: "./executor-pool" - dockerfile: "Dockerfile" - target: development - ports: - - "8083:8081" - - "5007:5005" - volumes: - - ./executor-pool/src:/opt/app/src - - ./executor-pool/target:/opt/app/target - executor1: - container_name: executor1 - build: - context: "./executor1" - dockerfile: "Dockerfile" - target: development - ports: - - "8084:8081" - - "5008:5005" - volumes: - - ./executor1/src:/opt/app/src - - ./executor1/target:/opt/app/target - executor2: - container_name: executor2 - build: - context: "./executor2" - dockerfile: "Dockerfile" - target: development - ports: - - "8085:8081" - - "5009:5005" - volumes: - - ./executor2/src:/opt/app/src - - ./executor2/target:/opt/app/target + tapas-tasks: + container_name: tapas-tasks + build: + context: "./tapas-tasks" + dockerfile: "Dockerfile" + target: development + ports: + - "8081:8081" + - "5005:5005" + volumes: + - ./tapas-tasks/src:/opt/app/src + - ./tapas-tasks/target:/opt/app/target + assignment: + container_name: assignment + build: + context: "./assignment" + dockerfile: "Dockerfile" + target: development + ports: + - "8082:8082" + - "5006:5005" + volumes: + - ./assignment/src:/opt/app/src + - ./assignment/target:/opt/app/target + executor-pool: + container_name: executor-pool + build: + context: "./executor-pool" + dockerfile: "Dockerfile" + target: development + ports: + - "8083:8081" + - "5007:5005" + volumes: + - ./executor-pool/src:/opt/app/src + - ./executor-pool/target:/opt/app/target + executor1: + container_name: executor1 + build: + context: "./executor1" + dockerfile: "Dockerfile" + target: development + ports: + - "8084:8081" + - "5008:5005" + volumes: + - ./executor1/src:/opt/app/src + - ./executor1/target:/opt/app/target + executor2: + container_name: executor2 + build: + context: "./executor2" + dockerfile: "Dockerfile" + target: development + ports: + - "8085:8085" + - "5009:5005" + volumes: + - ./executor2/src:/opt/app/src + - ./executor2/target:/opt/app/target diff --git a/executor-base/pom.xml b/executor-base/pom.xml index 6ad675a..32ec88a 100644 --- a/executor-base/pom.xml +++ b/executor-base/pom.xml @@ -56,6 +56,12 @@ javax.transaction-api 1.2 + + + org.json + json + 20210307 + diff --git a/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java b/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java index 1d4a80a..5119ac5 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java @@ -21,6 +21,7 @@ public class SelfValidating { * instance. */ protected void validateSelf() { + @SuppressWarnings("unchecked") Set> violations = validator.validate((T) this); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java index 75d3a02..182f2ba 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java @@ -21,7 +21,7 @@ public class TaskAvailableController { @GetMapping(path = "/newtask/{taskType}") public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { - + if (ExecutorType.contains(taskType.toUpperCase())) { TaskAvailableCommand command = new TaskAvailableCommand( ExecutorType.valueOf(taskType.toUpperCase())); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 2f53a2b..e688868 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -8,6 +8,9 @@ import java.net.http.HttpResponse; import java.util.HashMap; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.json.JSONObject; + import com.fasterxml.jackson.core.JsonProcessingException; import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort; @@ -20,39 +23,30 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort @Override public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { - ///Here we would need to work with DTOs in case the payload of calls becomes more complex - var values = new HashMap() {{ - put("result",event.getResult()); - put("status",event.getStatus()); - }}; - - var objectMapper = new ObjectMapper(); - String requestBody = null; - try { - requestBody = objectMapper.writeValueAsString(values); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } + String body = new JSONObject() + .put("taskID", event.getTaskID()) + .put("result", event.getResult()) + .put("status", event.getStatus()) + .toString(); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(server+"/task/"+event.getTaskID())) - .PUT(HttpRequest.BodyPublishers.ofString(requestBody)) + .header("Content-Type", "application/json") + .PUT(HttpRequest.BodyPublishers.ofString(body)) .build(); - /** Needs the other service running try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { e.printStackTrace(); + // Restore interrupted state... + Thread.currentThread().interrupt(); } - **/ System.out.println("Finish execution event sent with result:" + event.getResult()); - + } - + } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index b3e7875..b558075 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -1,8 +1,10 @@ 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 org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -11,6 +13,8 @@ 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 { @@ -19,27 +23,36 @@ public class GetAssignmentAdapter implements GetAssignmentPort { String server = "http://127.0.0.1:8082"; @Override - public Task getAssignment(ExecutorType executorType) { - + public Task getAssignment(ExecutorType executorType, String ip, int port) { + + String body = new JSONObject() + .put("executorType", executorType) + .put("ip", ip) + .put("port", port) + .toString(); + HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server+"/assignment/" + executorType)) - .GET() + .uri(URI.create(server+"/task/apply")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); - /** Needs the other service running try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { + if (response.body().equals("")) { + return null; + } + + return new Task(new JSONObject(response.body()).getString("taskID")); + + } catch (IOException | InterruptedException e) { e.printStackTrace(); + // Restore interrupted state... + Thread.currentThread().interrupt(); } - **/ - // TODO return null or a new Task here depending on the response of the http call - - return new Task("123"); + return null; } - + } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java index 916c8eb..cfa32bb 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java @@ -9,7 +9,7 @@ import lombok.Value; @Value public class TaskAvailableCommand extends SelfValidating { - + @NotNull private final ExecutorType taskType; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java index 1f205b8..79d3a0a 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java @@ -4,5 +4,5 @@ import ch.unisg.executorBase.executor.domain.ExecutorType; import ch.unisg.executorBase.executor.domain.Task; public interface GetAssignmentPort { - Task getAssignment(ExecutorType executorType); + Task getAssignment(ExecutorType executorType, String ip, int port); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index e639cb3..c9df1a8 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -4,13 +4,6 @@ import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEven import ch.unisg.executorBase.executor.application.port.out.GetAssignmentPort; import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; -import java.util.concurrent.TimeUnit; - -import javax.transaction.Transactional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; - 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; @@ -44,23 +37,19 @@ public abstract class ExecutorBase { this.ip = "localhost"; this.port = 8084; this.executorType = executorType; - + this.status = ExecutorStatus.STARTING_UP; if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { System.exit(0); } else { - System.out.println(true); this.status = ExecutorStatus.IDLING; getAssignment(); } } - // public static ExecutorBase getExecutor() { - // return executor; - // } - public void getAssignment() { - Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); + Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getIp(), + this.getPort()); if (newTask != null) { this.executeTask(newTask); } else { @@ -71,15 +60,16 @@ public abstract class ExecutorBase { private void executeTask(Task task) { System.out.println("Starting execution"); this.status = ExecutorStatus.EXECUTING; - + task.setResult(execution()); - executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); + executionFinishedEventPort.publishExecutionFinishedEvent( + new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); System.out.println("Finish execution"); getAssignment(); } protected abstract String execution(); - + } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java index 6719613..fec330f 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java @@ -1,11 +1,10 @@ package ch.unisg.executorBase.executor.domain; -import lombok.Data; import lombok.Getter; import lombok.Setter; public class Task { - + @Getter private String taskID; diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java new file mode 100644 index 0000000..db1b4b5 --- /dev/null +++ b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java @@ -0,0 +1,36 @@ +package ch.unisg.executor2.executor.adapter.in.web; + +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 retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { + + if (ExecutorType.contains(taskType.toUpperCase())) { + TaskAvailableCommand command = new TaskAvailableCommand( + ExecutorType.valueOf(taskType.toUpperCase())); + taskAvailableUseCase.newTaskAvailable(command); + } + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + + return new ResponseEntity<>("OK", responseHeaders, HttpStatus.OK); + } +} diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 4484ebb..6fa918d 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -20,7 +20,7 @@ public class TaskAvailableService implements TaskAvailableUseCase { Executor executor = Executor.getExecutor(); - if (executor.getExecutorType() == command.getTaskType() && + if (executor.getExecutorType() == command.getTaskType() && executor.getStatus() == ExecutorStatus.IDLING) { executor.getAssignment(); } diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index bb9308b..6aa1656 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -5,7 +5,7 @@ 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() { @@ -19,7 +19,7 @@ public class Executor extends ExecutorBase { @Override protected String execution() { - + int a = 20; int b = 20; try { diff --git a/executor2/src/main/resources/application.properties b/executor2/src/main/resources/application.properties index 4d360de..cd2d02b 100644 --- a/executor2/src/main/resources/application.properties +++ b/executor2/src/main/resources/application.properties @@ -1 +1 @@ -server.port=8081 +server.port=8085 -- 2.45.1 From 46969deb0f9df05669c7efb0757cc372b171d63a Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 10:33:26 +0200 Subject: [PATCH 18/94] added valueObjects & CustomErrors --- .../in/web/ApplyForTaskController.java | 15 +++------ .../adapter/in/web/NewTaskController.java | 15 +++------ .../in/web/WebControllerExceptionHandler.java | 31 +++++++++++++++++++ .../out/web/PublishNewTaskEventAdapter.java | 4 +-- .../web/PublishTaskAssignedEventAdapter.java | 2 +- .../web/PublishTaskCompletedEventAdapter.java | 2 +- .../port/in/ApplyForTaskCommand.java | 11 ++++--- .../application/port/in/NewTaskCommand.java | 8 +++-- .../port/in/TaskCompletedCommand.java | 5 +-- .../port/out/NewTaskEventPort.java | 2 +- .../port/out/TaskAssignedEventPort.java | 2 +- .../port/out/TaskCompletedEventPort.java | 2 +- .../service/ApplyForTaskService.java | 2 +- .../application/service/NewTaskService.java | 6 ++-- .../service/TaskCompletedService.java | 2 +- .../assignment/domain/ExecutorInfo.java | 9 ++++-- .../assignment/domain/NewTaskEvent.java | 9 ------ .../assignment/assignment/domain/Roster.java | 22 +++++++------ .../assignment/domain/RosterItem.java | 8 +++-- .../assignment/assignment/domain/Task.java | 10 +++++- .../assignment/domain/event/NewTaskEvent.java | 11 +++++++ .../domain/{ => event}/TaskAssignedEvent.java | 2 +- .../{ => event}/TaskCompletedEvent.java | 8 ++--- .../domain/valueobject/ExecutorType.java | 12 +++++++ .../domain/valueobject/IP4Adress.java | 23 ++++++++++++++ .../assignment/domain/valueobject/Port.java | 17 ++++++++++ .../common/exception/ErrorResponse.java | 13 ++++++++ .../common/exception/InvalidIP4Exception.java | 7 +++++ .../exception/PortOutOfRangeException.java | 7 +++++ .../web/ExecutionFinishedEventAdapter.java | 5 --- .../out/web/NotifyExecutorPoolAdapter.java | 28 +++++------------ executor2/pom.xml | 6 ++++ .../in/web/TaskAvailableController.java | 9 +++--- .../service/TaskAvailableService.java | 2 ++ 34 files changed, 214 insertions(+), 103 deletions(-) create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java delete mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java rename assignment/src/main/java/ch/unisg/assignment/assignment/domain/{ => event}/TaskAssignedEvent.java (73%) rename assignment/src/main/java/ch/unisg/assignment/assignment/domain/{ => event}/TaskCompletedEvent.java (58%) create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java index e83e74d..1d0111d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java @@ -1,12 +1,8 @@ package ch.unisg.assignment.assignment.adapter.in.web; -import javax.validation.ConstraintViolationException; - -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand; import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; @@ -23,14 +19,11 @@ public class ApplyForTaskController { @PostMapping(path = "/task/apply", consumes = {"application/json"}) public Task applyForTask(@RequestBody ExecutorInfo executorInfo) { - try { - ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(), - executorInfo.getIp(), executorInfo.getPort()); - return applyForTaskUseCase.applyForTask(command); + ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(), + executorInfo.getIp(), executorInfo.getPort()); + + return applyForTaskUseCase.applyForTask(command); - } catch (ConstraintViolationException e) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); - } } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java index d95fdd5..18bad8f 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java @@ -1,13 +1,10 @@ package ch.unisg.assignment.assignment.adapter.in.web; -import javax.validation.ConstraintViolationException; - import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; @@ -22,11 +19,9 @@ public class NewTaskController { } @PostMapping(path = "/task", consumes = {"application/json"}) - public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { - try { - NewTaskCommand command = new NewTaskCommand( - task.getTaskID(), task.getTaskType() - ); + public ResponseEntity newTaskController(@RequestBody Task task) { + + NewTaskCommand command = new NewTaskCommand(task.getTaskID(), task.getTaskType()); boolean success = newTaskUseCase.addNewTaskToQueue(command); @@ -34,8 +29,6 @@ public class NewTaskController { return new ResponseEntity<>(HttpStatus.CREATED); } return new ResponseEntity<>(HttpStatus.CONFLICT); - } catch (ConstraintViolationException e) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); - } + } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java new file mode 100644 index 0000000..08a0895 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java @@ -0,0 +1,31 @@ +package ch.unisg.assignment.assignment.adapter.in.web; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import ch.unisg.assignment.common.exception.ErrorResponse; +import ch.unisg.assignment.common.exception.InvalidIP4Exception; +import ch.unisg.assignment.common.exception.PortOutOfRangeException; + +@ControllerAdvice +public class WebControllerExceptionHandler { + + @ExceptionHandler(PortOutOfRangeException.class) + public ResponseEntity handleException(PortOutOfRangeException e){ + + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage()); + return new ResponseEntity<>(error, error.getHttpStatus()); + + } + + @ExceptionHandler(InvalidIP4Exception.class) + public ResponseEntity handleException(InvalidIP4Exception e){ + + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage()); + return new ResponseEntity<>(error, error.getHttpStatus()); + + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java index c08651b..b764faa 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -10,7 +10,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; -import ch.unisg.assignment.assignment.domain.NewTaskEvent; +import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; @Component @Primary @@ -23,7 +23,7 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/newtask/" + event.taskType)) + .uri(URI.create(server + "/newtask/" + event.taskType.getValue())) .GET() .build(); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java index 7f38d60..99879f7 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; -import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; @Component @Primary diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java index d63d710..59a27ce 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; -import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; @Component @Primary diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java index 76d0e41..df36d58 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java @@ -2,6 +2,9 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; +import ch.unisg.assignment.assignment.domain.valueobject.Port; import ch.unisg.assignment.common.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; @@ -11,16 +14,16 @@ import lombok.Value; public class ApplyForTaskCommand extends SelfValidating{ @NotNull - private final String taskType; + private final ExecutorType taskType; @NotNull - private final String executorIP; + private final IP4Adress executorIP; @NotNull - private final int executorPort; + private final Port executorPort; - public ApplyForTaskCommand(String taskType, String executorIP, int executorPort) { + public ApplyForTaskCommand(ExecutorType taskType, IP4Adress executorIP, Port executorPort) { this.taskType = taskType; this.executorIP = executorIP; this.executorPort = executorPort; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java index 98f4579..ab6838e 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java @@ -2,20 +2,22 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; import ch.unisg.assignment.common.SelfValidating; - +import lombok.EqualsAndHashCode; import lombok.Value; @Value +@EqualsAndHashCode(callSuper=false) public class NewTaskCommand extends SelfValidating { @NotNull private final String taskID; @NotNull - private final String taskType; + private final ExecutorType taskType; - public NewTaskCommand(String taskID, String taskType) { + public NewTaskCommand(String taskID, ExecutorType taskType) { this.taskID = taskID; this.taskType = taskType; this.validateSelf(); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java index 474456d..e324e89 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java @@ -2,6 +2,7 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; import ch.unisg.assignment.common.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; @@ -14,7 +15,7 @@ public class TaskCompletedCommand extends SelfValidating{ private final String taskID; @NotNull - private final String taskType; + private final ExecutorType taskType; @NotNull private final String taskStatus; @@ -22,7 +23,7 @@ public class TaskCompletedCommand extends SelfValidating{ @NotNull private final String taskResult; - public TaskCompletedCommand(String taskID, String taskType, String taskStatus, String taskResult) { + public TaskCompletedCommand(String taskID, ExecutorType taskType, String taskStatus, String taskResult) { this.taskID = taskID; this.taskType = taskType; this.taskStatus = taskStatus; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java index 521af1a..909a9ba 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java @@ -1,6 +1,6 @@ package ch.unisg.assignment.assignment.application.port.out; -import ch.unisg.assignment.assignment.domain.NewTaskEvent; +import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; public interface NewTaskEventPort { void publishNewTaskEvent(NewTaskEvent event); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java index 0cd1cae..fefd4a1 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java @@ -1,6 +1,6 @@ package ch.unisg.assignment.assignment.application.port.out; -import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; public interface TaskAssignedEventPort { void publishTaskAssignedEvent(TaskAssignedEvent taskAssignedEvent); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java index 37b4ff3..43a8aa5 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java @@ -1,6 +1,6 @@ package ch.unisg.assignment.assignment.application.port.out; -import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; public interface TaskCompletedEventPort { void publishTaskCompleted(TaskCompletedEvent event); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java index dac9a70..0593a30 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java @@ -9,7 +9,7 @@ import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; import ch.unisg.assignment.assignment.domain.Roster; import ch.unisg.assignment.assignment.domain.Task; -import ch.unisg.assignment.assignment.domain.TaskAssignedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java index b662cd2..069ee29 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java @@ -10,9 +10,9 @@ import org.springframework.stereotype.Component; import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; -import ch.unisg.assignment.assignment.domain.NewTaskEvent; import ch.unisg.assignment.assignment.domain.Roster; import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -26,9 +26,9 @@ public class NewTaskService implements NewTaskUseCase { public boolean addNewTaskToQueue(NewTaskCommand command) { // TODO Get availableTaskTypes from executor pool - List availableTaskTypes = Arrays.asList("addition", "robot"); + List availableTaskTypes = Arrays.asList("ADDITION", "ROBOT"); - if (!availableTaskTypes.contains(command.getTaskType())) { + if (!availableTaskTypes.contains(command.getTaskType().getValue())) { return false; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java index 1295336..c8273ff 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java @@ -8,7 +8,7 @@ import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand; import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase; import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; import ch.unisg.assignment.assignment.domain.Roster; -import ch.unisg.assignment.assignment.domain.TaskCompletedEvent; +import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java index 3b7432c..6b19dcc 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java @@ -1,18 +1,21 @@ package ch.unisg.assignment.assignment.domain; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; +import ch.unisg.assignment.assignment.domain.valueobject.Port; import lombok.Getter; import lombok.Setter; public class ExecutorInfo { @Getter @Setter - private String ip; + private IP4Adress ip; @Getter @Setter - private int port; + private Port port; @Getter @Setter - private String executorType; + private ExecutorType executorType; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java deleted file mode 100644 index 57084ee..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/NewTaskEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package ch.unisg.assignment.assignment.domain; - -public class NewTaskEvent { - public String taskType; - - public NewTaskEvent(String taskType) { - this.taskType = taskType; - } -} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java index 054c094..521a748 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java @@ -4,6 +4,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; +import ch.unisg.assignment.assignment.domain.valueobject.Port; + public class Roster { private static final Roster roster = new Roster(); @@ -19,25 +23,25 @@ public class Roster { private Roster() {} public void addTaskToQueue(Task task) { - if (queues.containsKey(task.getTaskType().toUpperCase())) { - queues.get(task.getTaskType().toUpperCase()).add(task); + if (queues.containsKey(task.getTaskType().getValue())) { + queues.get(task.getTaskType().getValue()).add(task); } else { - queues.put(task.getTaskType().toUpperCase(), new ArrayList<>(Arrays.asList(task))); + queues.put(task.getTaskType().getValue(), new ArrayList<>(Arrays.asList(task))); } } - public Task assignTaskToExecutor(String taskType, String executorIP, int executorPort) { - if (!queues.containsKey(taskType.toUpperCase())) { + public Task assignTaskToExecutor(ExecutorType taskType, IP4Adress executorIP, Port executorPort) { + if (!queues.containsKey(taskType.getValue())) { return null; } - if (queues.get(taskType.toUpperCase()).isEmpty()) { + if (queues.get(taskType.getValue()).isEmpty()) { return null; } - Task task = queues.get(taskType.toUpperCase()).remove(0); + Task task = queues.get(taskType.getValue()).remove(0); - rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(), task.getTaskType(), - executorIP, executorPort)); + rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(), + task.getTaskType().getValue(), executorIP, executorPort)); return task; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java index 6e050d4..2c3bb52 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java @@ -1,5 +1,7 @@ package ch.unisg.assignment.assignment.domain; +import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; +import ch.unisg.assignment.assignment.domain.valueobject.Port; import lombok.Getter; public class RosterItem { @@ -11,13 +13,13 @@ public class RosterItem { private String taskType; @Getter - private String executorIP; + private IP4Adress executorIP; @Getter - private int executorPort; + private Port executorPort; - public RosterItem(String taskID, String taskType, String executorIP, int executorPort) { + public RosterItem(String taskID, String taskType, IP4Adress executorIP, Port executorPort) { this.taskID = taskID; this.taskType = taskType; this.executorIP = executorIP; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java index 6db5111..55fb00d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java @@ -1,5 +1,6 @@ package ch.unisg.assignment.assignment.domain; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; import lombok.Getter; import lombok.Setter; @@ -9,7 +10,7 @@ public class Task { private String taskID; @Getter - private String taskType; + private ExecutorType taskType; @Getter @Setter @@ -20,8 +21,15 @@ public class Task { private String status; public Task(String taskID, String taskType) { + this.taskID = taskID; + this.taskType = new ExecutorType(taskType); + } + + public Task(String taskID, ExecutorType taskType) { this.taskID = taskID; this.taskType = taskType; } + public Task() {}; + } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java new file mode 100644 index 0000000..34e7f0b --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java @@ -0,0 +1,11 @@ +package ch.unisg.assignment.assignment.domain.event; + +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; + +public class NewTaskEvent { + public final ExecutorType taskType; + + public NewTaskEvent(ExecutorType taskType) { + this.taskType = taskType; + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java similarity index 73% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java rename to assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java index 8acd144..ef20f43 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskAssignedEvent.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.assignment.assignment.domain.event; public class TaskAssignedEvent { public String taskID; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java similarity index 58% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java rename to assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java index 89f26ae..432a8f0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/TaskCompletedEvent.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java @@ -1,11 +1,11 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.assignment.assignment.domain.event; public class TaskCompletedEvent { - public String taskID; + public final String taskID; - public String status; + public final String status; - public String result; + public final String result; public TaskCompletedEvent(String taskID, String status, String result) { this.taskID = taskID; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java new file mode 100644 index 0000000..bc5f467 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java @@ -0,0 +1,12 @@ +package ch.unisg.assignment.assignment.domain.valueobject; + +import lombok.Value; + +@Value +public class ExecutorType { + private String value; + + public ExecutorType(String type) { + this.value = type.toUpperCase(); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java new file mode 100644 index 0000000..cd23b6b --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java @@ -0,0 +1,23 @@ +package ch.unisg.assignment.assignment.domain.valueobject; + +import ch.unisg.assignment.common.exception.InvalidIP4Exception; +import lombok.Value; + +@Value +public class IP4Adress { + private String value; + + public IP4Adress(String ip4) throws InvalidIP4Exception { + if (ip4.equalsIgnoreCase("localhost") || + ip4.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { + this.value = ip4; + } else { + throw new InvalidIP4Exception(); + } + } +} + + + + + diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java new file mode 100644 index 0000000..a66dbbd --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java @@ -0,0 +1,17 @@ +package ch.unisg.assignment.assignment.domain.valueobject; + +import ch.unisg.assignment.common.exception.PortOutOfRangeException; +import lombok.Value; + +@Value +public class Port { + private int value; + + public Port(int port) throws PortOutOfRangeException { + if (1024 <= port && port <= 65535) { + this.value = port; + } else { + throw new PortOutOfRangeException(); + } + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java b/assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java new file mode 100644 index 0000000..2fb834e --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java @@ -0,0 +1,13 @@ +package ch.unisg.assignment.common.exception; + +import org.springframework.http.HttpStatus; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class ErrorResponse { + private final HttpStatus httpStatus; + private final String message; +} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java b/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java new file mode 100644 index 0000000..fecbfcb --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.common.exception; + +public class InvalidIP4Exception extends Exception { + public InvalidIP4Exception() { + super("IP4 is invalid"); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java b/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java new file mode 100644 index 0000000..2772256 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java @@ -0,0 +1,7 @@ +package ch.unisg.assignment.common.exception; + +public class PortOutOfRangeException extends Exception { + public PortOutOfRangeException() { + super("Port is out of available range (1024-65535)"); + } +} diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index e688868..7408740 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -5,14 +5,9 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.HashMap; - -import com.fasterxml.jackson.databind.ObjectMapper; import org.json.JSONObject; -import com.fasterxml.jackson.core.JsonProcessingException; - import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort; import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index feeca69..bf465f7 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -3,11 +3,8 @@ package ch.unisg.executorBase.executor.adapter.out.web; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; -import java.util.HashMap; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.json.JSONObject; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -17,31 +14,22 @@ import ch.unisg.executorBase.executor.domain.ExecutorType; @Component @Primary public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { - - //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) + String server = "http://127.0.0.1:8083"; @Override public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { - var values = new HashMap() {{ - put("ip", ip); - put("port", Integer.toString(port)); - put("executorType", executorType.toString()); - }}; - - var objectMapper = new ObjectMapper(); - String requestBody = null; - try { - requestBody = objectMapper.writeValueAsString(values); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } + String body = new JSONObject() + .put("executorType", executorType) + .put("ip", ip) + .put("port", port) + .toString(); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(server+"/executor/new/")) - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); /** Needs the other service running diff --git a/executor2/pom.xml b/executor2/pom.xml index 95c67ff..86634c6 100644 --- a/executor2/pom.xml +++ b/executor2/pom.xml @@ -45,6 +45,12 @@ executorBase 0.0.1-SNAPSHOT + + + org.json + json + 20210307 + diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java index db1b4b5..a14b58f 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java @@ -1,5 +1,7 @@ package ch.unisg.executor2.executor.adapter.in.web; +import java.util.concurrent.CompletableFuture; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -25,12 +27,9 @@ public class TaskAvailableController { if (ExecutorType.contains(taskType.toUpperCase())) { TaskAvailableCommand command = new TaskAvailableCommand( ExecutorType.valueOf(taskType.toUpperCase())); - taskAvailableUseCase.newTaskAvailable(command); + CompletableFuture.runAsync(() -> taskAvailableUseCase.newTaskAvailable(command)); } - // Add the content type as a response header - HttpHeaders responseHeaders = new HttpHeaders(); - - return new ResponseEntity<>("OK", responseHeaders, HttpStatus.OK); + return new ResponseEntity<>("OK", new HttpHeaders(), HttpStatus.OK); } } diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 6fa918d..39db6ab 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -1,5 +1,6 @@ package ch.unisg.executor2.executor.application.service; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import ch.unisg.executor2.executor.domain.Executor; @@ -16,6 +17,7 @@ import javax.transaction.Transactional; public class TaskAvailableService implements TaskAvailableUseCase { @Override + @Async public void newTaskAvailable(TaskAvailableCommand command) { Executor executor = Executor.getExecutor(); -- 2.45.1 From 39191afc2c5fc0d2692f31a5e7ba87c2ec765b56 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 10:44:47 +0200 Subject: [PATCH 19/94] fixed security hotspots & code smells --- .../adapter/out/web/PublishTaskAssignedEventAdapter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java index 99879f7..85bb9ab 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -5,6 +5,8 @@ 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.context.annotation.Primary; @@ -19,6 +21,8 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { String server = "http://127.0.0.1:8085"; + Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName()); + @Override public void publishTaskAssignedEvent(TaskAssignedEvent event) { @@ -37,7 +41,7 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); // Restore interrupted state... Thread.currentThread().interrupt(); } -- 2.45.1 From b072cfc7ce5758f2009c8bbe791af7519dd9aa61 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 10:45:24 +0200 Subject: [PATCH 20/94] fixed security hotspots & code smells --- .../adapter/out/web/PublishNewTaskEventAdapter.java | 6 +++++- .../adapter/out/web/PublishTaskCompletedEventAdapter.java | 6 +++++- .../java/ch/unisg/assignment/assignment/domain/Task.java | 2 +- .../assignment/domain/event/TaskAssignedEvent.java | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java index b764faa..1db2b84 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -5,6 +5,8 @@ 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.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -18,6 +20,8 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { String server = "http://127.0.0.1:8085"; + Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); + @Override public void publishNewTaskEvent(NewTaskEvent event) { @@ -31,7 +35,7 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); // Restore interrupted state... Thread.currentThread().interrupt(); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java index 59a27ce..f9f2833 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -5,6 +5,8 @@ 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.context.annotation.Primary; @@ -19,6 +21,8 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort String server = "http://127.0.0.1:8081"; + Logger logger = Logger.getLogger(PublishTaskCompletedEventAdapter.class.getName()); + @Override public void publishTaskCompleted(TaskCompletedEvent event) { @@ -39,7 +43,7 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); // Restore interrupted state... Thread.currentThread().interrupt(); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java index 55fb00d..7daa738 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java @@ -30,6 +30,6 @@ public class Task { this.taskType = taskType; } - public Task() {}; + public Task() {} } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java index ef20f43..d0178d4 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java @@ -1,7 +1,7 @@ package ch.unisg.assignment.assignment.domain.event; public class TaskAssignedEvent { - public String taskID; + public final String taskID; public TaskAssignedEvent(String taskID) { this.taskID = taskID; -- 2.45.1 From 812f763620002a3c320ffffde203eca2de659baf Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 11:01:38 +0200 Subject: [PATCH 21/94] removed sonar config from executor base pom --- executor-base/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/executor-base/pom.xml b/executor-base/pom.xml index 32ec88a..96dec99 100644 --- a/executor-base/pom.xml +++ b/executor-base/pom.xml @@ -15,8 +15,6 @@ Demo project for Spring Boot 11 - scs-asse-fs21-group1 - https://sonarcloud.io -- 2.45.1 From 6cb52ba0c65ed1009a60cdc87ee4332236991b54 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 11:07:34 +0200 Subject: [PATCH 22/94] Added placeholder bean --- .../service/TaskAvailableService.java | 20 +++++++++++++++++++ .../service/TaskAvailableService.java | 2 -- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java new file mode 100644 index 0000000..a4f5e6e --- /dev/null +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java @@ -0,0 +1,20 @@ +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 + } +} diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 39db6ab..6fa918d 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -1,6 +1,5 @@ package ch.unisg.executor2.executor.application.service; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import ch.unisg.executor2.executor.domain.Executor; @@ -17,7 +16,6 @@ import javax.transaction.Transactional; public class TaskAvailableService implements TaskAvailableUseCase { @Override - @Async public void newTaskAvailable(TaskAvailableCommand command) { Executor executor = Executor.getExecutor(); -- 2.45.1 From d1d584bf6c5b1e56adcd83591fd119b36c66bf9d Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 12:12:29 +0200 Subject: [PATCH 23/94] build error fixes --- executor-base/pom.xml | 17 ----------------- .../out/web/ExecutionFinishedEventAdapter.java | 7 +++++-- .../adapter/out/web/GetAssignmentAdapter.java | 7 +++++-- executor2/pom.xml | 1 + 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/executor-base/pom.xml b/executor-base/pom.xml index 96dec99..2acba18 100644 --- a/executor-base/pom.xml +++ b/executor-base/pom.xml @@ -62,21 +62,4 @@ - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 7408740..fd26d47 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -5,6 +5,8 @@ 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; @@ -13,9 +15,10 @@ import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { - //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) String server = "http://127.0.0.1:8082"; + Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName()); + @Override public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { @@ -35,7 +38,7 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); // Restore interrupted state... Thread.currentThread().interrupt(); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index b558075..05852fa 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -5,6 +5,8 @@ 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.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -19,9 +21,10 @@ import org.json.JSONObject; @Primary public class GetAssignmentAdapter implements GetAssignmentPort { - //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) String server = "http://127.0.0.1:8082"; + Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName()); + @Override public Task getAssignment(ExecutorType executorType, String ip, int port) { @@ -47,7 +50,7 @@ public class GetAssignmentAdapter implements GetAssignmentPort { return new Task(new JSONObject(response.body()).getString("taskID")); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); // Restore interrupted state... Thread.currentThread().interrupt(); } diff --git a/executor2/pom.xml b/executor2/pom.xml index 86634c6..1f970e0 100644 --- a/executor2/pom.xml +++ b/executor2/pom.xml @@ -44,6 +44,7 @@ ch.unisg executorBase 0.0.1-SNAPSHOT + compile -- 2.45.1 From 35f7ad67f776bd0d981687278222d6cee213a68a Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 12:14:01 +0200 Subject: [PATCH 24/94] ci build fixes --- .github/workflows/ci.executor1.yml | 2 +- .github/workflows/ci.executor2.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.executor1.yml b/.github/workflows/ci.executor1.yml index 5d48580..708d7d4 100644 --- a/.github/workflows/ci.executor1.yml +++ b/.github/workflows/ci.executor1.yml @@ -37,7 +37,7 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Build executorBase - run: mvn -f executor-base/pom.xml -B verify + run: mvn -f executor-base/pom.xml -B install - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.executor2.yml b/.github/workflows/ci.executor2.yml index 32a59a8..5ae38f0 100644 --- a/.github/workflows/ci.executor2.yml +++ b/.github/workflows/ci.executor2.yml @@ -37,7 +37,7 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Build executorBase - run: mvn -f executor-base/pom.xml -B verify + run: mvn -f executor-base/pom.xml -B install - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -- 2.45.1 From 4ee640e6888796cefa8b0a640ef2648aa598f771 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 12:29:18 +0200 Subject: [PATCH 25/94] Added missing class and chaged port --- .../in/web/TaskAvailableController.java | 36 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java new file mode 100644 index 0000000..5501885 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -0,0 +1,36 @@ +package ch.unisg.executor1.executor.adapter.in.web; + +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 retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { + + if (ExecutorType.contains(taskType.toUpperCase())) { + TaskAvailableCommand command = new TaskAvailableCommand( + ExecutorType.valueOf(taskType.toUpperCase())); + taskAvailableUseCase.newTaskAvailable(command); + } + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + + return new ResponseEntity<>("OK", responseHeaders, HttpStatus.OK); + } +} diff --git a/executor1/src/main/resources/application.properties b/executor1/src/main/resources/application.properties index 4d360de..5e3bb81 100644 --- a/executor1/src/main/resources/application.properties +++ b/executor1/src/main/resources/application.properties @@ -1 +1 @@ -server.port=8081 +server.port=8084 -- 2.45.1 From f461c5f3cbc530958b35d0953c2d16288cafaf1c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 15:38:04 +0200 Subject: [PATCH 26/94] integration + fixes --- .deployment/docker-compose.yml | 157 ++++++++++++------ .github/workflows/build-and-deploy.yml | 3 + app/pom.xml | 44 ----- .../main/java/com/app/hello/Application.java | 19 --- .../java/com/app/hello/HelloController.java | 14 -- .../test/java/com/app/hello/DummyTest.java | 13 -- .../in/web/TaskCompletedController.java | 2 +- ...llExecutorInExecutorPoolByTypeAdapter.java | 55 ++++++ .../out/web/PublishNewTaskEventAdapter.java | 18 +- .../web/PublishTaskAssignedEventAdapter.java | 4 +- .../port/in/TaskCompletedCommand.java | 7 +- ...etAllExecutorInExecutorPoolByTypePort.java | 9 + .../application/service/NewTaskService.java | 6 +- .../web/ExecutionFinishedEventAdapter.java | 4 +- .../out/web/NotifyExecutorPoolAdapter.java | 33 ++-- .../adapter/in/web/ExecutorMediaType.java | 9 +- ...lExecutorsInExecutorPoolWebController.java | 1 - .../src/main/resources/application.properties | 2 +- .../in/web/TaskAvailableController.java | 4 +- .../executor1/executor/domain/Executor.java | 18 +- .../executor2/executor/domain/Executor.java | 2 +- .../in/web/CompleteTaskWebController.java | 1 + .../PublishNewTaskAddedEventWebAdapter.java | 9 +- .../service/AddNewTaskToTaskListService.java | 1 + .../service/DeleteTaskService.java | 3 + 25 files changed, 252 insertions(+), 186 deletions(-) delete mode 100644 app/pom.xml delete mode 100755 app/src/main/java/com/app/hello/Application.java delete mode 100755 app/src/main/java/com/app/hello/HelloController.java delete mode 100755 app/src/test/java/com/app/hello/DummyTest.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 6d8f256..b8397f3 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -1,57 +1,108 @@ version: "3.0" services: - reverse-proxy: - image: traefik:v2.1.3 - command: - - --entrypoints.web.address=:80 - - --entrypoints.websecure.address=:443 - - --providers.docker=true - - --certificatesResolvers.le.acme.httpChallenge.entryPoint=web - - --certificatesresolvers.le.acme.email=martin.eigenmann@unisg.ch - - --certificatesresolvers.le.acme.storage=/acme.json - - --providers.docker.exposedByDefault=false - - --serversTransport.insecureSkipVerify=true - ports: - - "80:80" - - "443:443" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./acme.json:/acme.json - restart: unless-stopped - labels: - - "traefik.enable=true" - - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" - - "traefik.http.routers.http-catchall.entrypoints=web" - - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" - - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - tapas-tasks: - image: openjdk - command: "java -jar /data/tapas-tasks-0.0.1-SNAPSHOT.jar" - restart: unless-stopped - volumes: - - ./:/data/ - labels: - - "traefik.enable=true" - - "traefik.http.routers.tapas-tasks.rule=Host(`tapas-tasks.${PUB_IP}.nip.io`)" - - "traefik.http.routers.tapas-tasks.service=tapas-tasks" - - "traefik.http.services.tapas-tasks.loadbalancer.server.port=8081" - - "traefik.http.routers.tapas-tasks.tls=true" - - "traefik.http.routers.tapas-tasks.entryPoints=web,websecure" - - "traefik.http.routers.tapas-tasks.tls.certresolver=le" + reverse-proxy: + image: traefik:v2.1.3 + command: + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --providers.docker=true + - --certificatesResolvers.le.acme.httpChallenge.entryPoint=web + - --certificatesresolvers.le.acme.email=martin.eigenmann@unisg.ch + - --certificatesresolvers.le.acme.storage=/acme.json + - --providers.docker.exposedByDefault=false + - --serversTransport.insecureSkipVerify=true + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./acme.json:/acme.json + restart: unless-stopped + labels: + - "traefik.enable=true" + - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" + - "traefik.http.routers.http-catchall.entrypoints=web" + - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - app: - image: openjdk - command: "java -jar /data/app-0.1.0.jar" - restart: unless-stopped - volumes: - - ./:/data/ - labels: - - "traefik.enable=true" - - "traefik.http.routers.app.rule=Host(`app.${PUB_IP}.nip.io`)" - - "traefik.http.routers.app.service=app" - - "traefik.http.services.app.loadbalancer.server.port=8080" - - "traefik.http.routers.app.tls=true" - - "traefik.http.routers.app.entryPoints=web,websecure" - - "traefik.http.routers.app.tls.certresolver=le" + tapas-tasks: + image: openjdk + command: "java -jar /data/tapas-tasks-0.0.1-SNAPSHOT.jar" + restart: unless-stopped + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.tapas-tasks.rule=Host(`tapas-tasks.${PUB_IP}.nip.io`)" + - "traefik.http.routers.tapas-tasks.service=tapas-tasks" + - "traefik.http.services.tapas-tasks.loadbalancer.server.port=8081" + - "traefik.http.routers.tapas-tasks.tls=true" + - "traefik.http.routers.tapas-tasks.entryPoints=web,websecure" + - "traefik.http.routers.tapas-tasks.tls.certresolver=le" + + assignment: + image: openjdk + command: "java -jar /data/assignment-0.0.1.jar" + restart: unless-stopped + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.assignment.rule=Host(`assignment.${PUB_IP}.nip.io`)" + - "traefik.http.routers.assignment.service=assignment" + - "traefik.http.services.assignment.loadbalancer.server.port=8082" + - "traefik.http.routers.assignment.tls=true" + - "traefik.http.routers.assignment.entryPoints=web,websecure" + - "traefik.http.routers.assignment.tls.certresolver=le" + + executor-pool: + image: openjdk + command: "java -jar /data/executor-pool-0.0.1.jar" + restart: unless-stopped + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.executor-pool.rule=Host(`executor-pool.${PUB_IP}.nip.io`)" + - "traefik.http.routers.executor-pool.service=executor-pool" + - "traefik.http.services.executor-pool.loadbalancer.server.port=8083" + - "traefik.http.routers.executor-pool.tls=true" + - "traefik.http.routers.executor-pool.entryPoints=web,websecure" + - "traefik.http.routers.executor-pool.tls.certresolver=le" + + executor1: + image: openjdk + command: "java -jar /data/executor1-0.0.1.jar" + restart: unless-stopped + depends_on: + - executor-pool + - assignment + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.executor1.rule=Host(`executor1.${PUB_IP}.nip.io`)" + - "traefik.http.routers.executor1.service=executor1" + - "traefik.http.services.executor1.loadbalancer.server.port=8084" + - "traefik.http.routers.executor1.tls=true" + - "traefik.http.routers.executor1.entryPoints=web,websecure" + - "traefik.http.routers.executor1.tls.certresolver=le" + + executor2: + image: openjdk + command: "java -jar /data/executor2-0.0.1.jar" + restart: unless-stopped + depends_on: + - executor-pool + - assignment + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.executor2.rule=Host(`executor2.${PUB_IP}.nip.io`)" + - "traefik.http.routers.executor2.service=executor2" + - "traefik.http.services.executor2.loadbalancer.server.port=8085" + - "traefik.http.routers.executor2.tls=true" + - "traefik.http.routers.executor2.entryPoints=web,websecure" + - "traefik.http.routers.executor2.tls.certresolver=le" diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 42b93ad..0429aca 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -41,6 +41,9 @@ jobs: run: mvn -f executor-pool/pom.xml --batch-mode --update-snapshots verify - run: cp ./executor-pool/target/executor-pool-0.0.1.jar ./target + - name: Build with Maven + run: mvn -f executorBase/pom.xml --batch-mode --update-snapshots install + - name: Build with Maven run: mvn -f executor1/pom.xml --batch-mode --update-snapshots verify - run: cp ./executor1/target/executor1-0.0.1.jar ./target diff --git a/app/pom.xml b/app/pom.xml deleted file mode 100644 index 5f4a1fe..0000000 --- a/app/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - 4.0.0 - - com.dockerforjavadevelopers - app - 0.1.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.2.1.RELEASE - - - - - org.springframework.boot - spring-boot-starter-web - - - - junit - junit - 4.13.1 - test - - - - - - com.dockerforjavadevelopers.hello.Application - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/app/src/main/java/com/app/hello/Application.java b/app/src/main/java/com/app/hello/Application.java deleted file mode 100755 index 4c5e45f..0000000 --- a/app/src/main/java/com/app/hello/Application.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.app.hello; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableAutoConfiguration -@ComponentScan -public class Application { - - public static void main(String[] args) { - ApplicationContext ctx = SpringApplication.run(Application.class, args); - - } - -} diff --git a/app/src/main/java/com/app/hello/HelloController.java b/app/src/main/java/com/app/hello/HelloController.java deleted file mode 100755 index d8f810b..0000000 --- a/app/src/main/java/com/app/hello/HelloController.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.app.hello; - -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RequestMapping; - -@RestController -public class HelloController { - - @RequestMapping("/") - public String index() { - return "Hello World! Nice to see you :-)\n"; - } - -} diff --git a/app/src/test/java/com/app/hello/DummyTest.java b/app/src/test/java/com/app/hello/DummyTest.java deleted file mode 100755 index 7447934..0000000 --- a/app/src/test/java/com/app/hello/DummyTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.app.hello; - -import static org.junit.Assert.*; - -import org.junit.Test; - -public class DummyTest { - - @Test - public void aTest() { - assertEquals(true, true); - } -} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java index e8335ed..cde4c0a 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java @@ -22,7 +22,7 @@ public class TaskCompletedController { @PostMapping(path = "/task/completed", consumes = {"application/json"}) public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { - TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), task.getTaskType(), + TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), task.getStatus(), task.getResult()); taskCompletedUseCase.taskCompleted(command); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java new file mode 100644 index 0000000..4163a53 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java @@ -0,0 +1,55 @@ +package ch.unisg.assignment.assignment.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.json.JSONArray; +import org.json.JSONObject; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.out.GetAllExecutorInExecutorPoolByTypePort; +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; + +@Component +@Primary +public class GetAllExecutorInExecutorPoolByTypeAdapter implements GetAllExecutorInExecutorPoolByTypePort { + + @Override + public boolean doesExecutorTypeExist(ExecutorType type) { + String server = "http://127.0.0.1:8083"; + + Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); + + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server + "/executor-pool/GetAllExecutorInExecutorPoolByType/" + type.getValue())) + .header("Content-Type", "application/json") + .GET() + .build(); + + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == HttpStatus.OK.value()) { + JSONArray jsonArray = new JSONArray(response.body().toString()); + if (jsonArray.length() > 0) { + return true; + } + } + } catch (IOException | InterruptedException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + return false; + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java index 1db2b84..5007c1c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -18,7 +18,8 @@ import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; @Primary public class PublishNewTaskEventAdapter implements NewTaskEventPort { - String server = "http://127.0.0.1:8085"; + String server = "http://127.0.0.1:8084"; + String server2 = "http://127.0.0.1:8085"; Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); @@ -32,6 +33,21 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { .build(); + try { + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + + HttpClient client2 = HttpClient.newHttpClient(); + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(server2 + "/newtask/" + event.taskType.getValue())) + .GET() + .build(); + + try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException | InterruptedException e) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java index 85bb9ab..56cb803 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -19,7 +19,7 @@ import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; @Primary public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { - String server = "http://127.0.0.1:8085"; + String server = "http://127.0.0.1:8081"; Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName()); @@ -32,7 +32,7 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/tasks/completeTask")) + .uri(URI.create(server + "/tasks/assignTask")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java index e324e89..b0af2b4 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java @@ -2,7 +2,6 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; import ch.unisg.assignment.common.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; @@ -14,18 +13,14 @@ public class TaskCompletedCommand extends SelfValidating{ @NotNull private final String taskID; - @NotNull - private final ExecutorType taskType; - @NotNull private final String taskStatus; @NotNull private final String taskResult; - public TaskCompletedCommand(String taskID, ExecutorType taskType, String taskStatus, String taskResult) { + public TaskCompletedCommand(String taskID, String taskStatus, String taskResult) { this.taskID = taskID; - this.taskType = taskType; this.taskStatus = taskStatus; this.taskResult = taskResult; this.validateSelf(); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java new file mode 100644 index 0000000..e751727 --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java @@ -0,0 +1,9 @@ +package ch.unisg.assignment.assignment.application.port.out; + +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; + +public interface GetAllExecutorInExecutorPoolByTypePort { + boolean doesExecutorTypeExist(ExecutorType type); +} + + diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java index 069ee29..7d7de5c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Component; import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; +import ch.unisg.assignment.assignment.application.port.out.GetAllExecutorInExecutorPoolByTypePort; import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; import ch.unisg.assignment.assignment.domain.Roster; import ch.unisg.assignment.assignment.domain.Task; @@ -21,14 +22,13 @@ import lombok.RequiredArgsConstructor; public class NewTaskService implements NewTaskUseCase { private final NewTaskEventPort newTaskEventPort; + private final GetAllExecutorInExecutorPoolByTypePort getAllExecutorInExecutorPoolByTypePort; @Override public boolean addNewTaskToQueue(NewTaskCommand command) { // TODO Get availableTaskTypes from executor pool - List availableTaskTypes = Arrays.asList("ADDITION", "ROBOT"); - - if (!availableTaskTypes.contains(command.getTaskType().getValue())) { + if (!getAllExecutorInExecutorPoolByTypePort.doesExecutorTypeExist(command.getTaskType())) { return false; } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index fd26d47..971e583 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -30,9 +30,9 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server+"/task/"+event.getTaskID())) + .uri(URI.create(server+"/task/completed")) .header("Content-Type", "application/json") - .PUT(HttpRequest.BodyPublishers.ofString(body)) + .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); try { diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index bf465f7..720b015 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -1,11 +1,16 @@ 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.json.JSONObject; import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; @@ -17,34 +22,36 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { String server = "http://127.0.0.1:8083"; + Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); + @Override public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { String body = new JSONObject() - .put("executorType", executorType) - .put("ip", ip) - .put("port", port) + .put("executorTaskType", executorType) + .put("executorIp", ip) + .put("executorPort", Integer.toString(port)) .toString(); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server+"/executor/new/")) + .uri(URI.create(server+"/executor-pool/AddExecutor")) + .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); - /** Needs the other service running try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); + if (response.statusCode() == HttpStatus.CREATED.value()) { + return true; + } + } catch (IOException | InterruptedException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); + // Restore interrupted state... + Thread.currentThread().interrupt(); } - **/ - // TODO return true or false depending on result of http request; - - return true; + return false; } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java index cbbc33b..0ca4e1f 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java @@ -23,10 +23,15 @@ final public class ExecutorMediaType { String serializedList = "[ \n"; for (ExecutorClass executor: listOfExecutors) { - serializedList += serialize(executor) + ",\n"; + serializedList += serialize(executor) + "\n"; } - return serializedList + "\n ]"; + // return serializedList + "\n ]"; + JSONArray jsonArray = new JSONArray(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("executorIp", "localhost"); + jsonArray.put(jsonObject); + return jsonArray.toString(); } private ExecutorMediaType() { } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java index ada219c..70a5fd2 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java @@ -2,7 +2,6 @@ package ch.unisg.executorpool.adapter.in.web; import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolUseCase; import ch.unisg.executorpool.domain.ExecutorClass; -import ch.unisg.tapastasks.tasks.adapter.in.web.TaskMediaType; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/executor-pool/src/main/resources/application.properties b/executor-pool/src/main/resources/application.properties index 4d360de..8f91ca7 100644 --- a/executor-pool/src/main/resources/application.properties +++ b/executor-pool/src/main/resources/application.properties @@ -1 +1 @@ -server.port=8081 +server.port=8083 diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java index 5501885..1f08545 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -1,5 +1,7 @@ package ch.unisg.executor1.executor.adapter.in.web; +import java.util.concurrent.CompletableFuture; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -25,7 +27,7 @@ public class TaskAvailableController { if (ExecutorType.contains(taskType.toUpperCase())) { TaskAvailableCommand command = new TaskAvailableCommand( ExecutorType.valueOf(taskType.toUpperCase())); - taskAvailableUseCase.newTaskAvailable(command); + CompletableFuture.runAsync(() -> taskAvailableUseCase.newTaskAvailable(command)); } // Add the content type as a response header diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java index 4a7734c..cc11e64 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -14,7 +14,7 @@ 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.ROBOT); private final UserToRobotPort userToRobotPort = new UserToRobotAdapter(); private final InstructionToRobotPort instructionToRobotPort = new InstructionToRobotAdapter(); @@ -31,12 +31,24 @@ public class Executor extends ExecutorBase { @Override protected String execution() { - + String key = userToRobotPort.userToRobot(); + try { + TimeUnit.MILLISECONDS.sleep(1500); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } boolean result1 = instructionToRobotPort.instructionToRobot(key); + try { + TimeUnit.MILLISECONDS.sleep(10000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } deleteUserFromRobotPort.deleteUserFromRobot(key); return Boolean.toString(result1); } -} \ No newline at end of file +} diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index 6aa1656..4d022b5 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -23,7 +23,7 @@ public class Executor extends ExecutorBase { int a = 20; int b = 20; try { - TimeUnit.SECONDS.sleep(10); + TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java index e160c2b..536b72c 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java @@ -23,6 +23,7 @@ public class CompleteTaskWebController { @PostMapping(path = "/tasks/completeTask", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) public ResponseEntity completeTask (@RequestBody Task task){ + try { CompleteTaskCommand command = new CompleteTaskCommand( task.getTaskId(), task.getTaskResult() diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java index 7ae8e90..d642eca 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java @@ -27,9 +27,7 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort //Here we would need to work with DTOs in case the payload of calls becomes more complex var values = new HashMap() {{ - put("taskname",event.taskName); - put("tasklist",event.taskListName); - put("taskId", event.taskId); + put("taskID", event.taskId); put("taskType", event.taskType); }}; @@ -43,11 +41,11 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server+"/roster/newtask/")) + .uri(URI.create(server+"/task")) + .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); - /** Needs the other service running try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException e) { @@ -55,6 +53,5 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort } catch (InterruptedException e) { e.printStackTrace(); } - **/ } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 2aa360c..24f68d0 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -30,6 +30,7 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase //the core and then the integration event in the application layer. if (newTask != null) { NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent(newTask.getTaskName().getValue(), + taskList.getTaskListName().getValue(), newTask.getTaskId().getValue(), newTask.getTaskType().getValue()); newTaskAddedEventPort.publishNewTaskAddedEvent(newTaskAdded); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java index 05d1da5..cfebcd6 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java @@ -18,6 +18,9 @@ public class DeleteTaskService implements DeleteTaskUseCase { @Override public Optional deleteTask(DeleteTaskCommand command){ + + // TODO check with assignment service if we can delte + TaskList taskList = TaskList.getTapasTaskList(); return taskList.deleteTaskById(command.getTaskId()); -- 2.45.1 From bb1ffb8bfd4bfdeec097531a763f4e05bf22a9b4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 15:44:09 +0200 Subject: [PATCH 27/94] bugfixes --- executor-pool/pom.xml | 6 ---- .../java/ch/unisg/common/SelfValidating.java | 30 +++++++++++++++++++ .../AddNewExecutorToExecutorPoolCommand.java | 2 +- ...tAllExecutorInExecutorPoolByTypeQuery.java | 2 +- ...RemoveExecutorFromExecutorPoolCommand.java | 2 +- .../Executor1ApplicationTests.java} | 4 +-- 6 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 executor-pool/src/main/java/ch/unisg/common/SelfValidating.java rename executor1/src/test/java/ch/unisg/{executor2/Executor2ApplicationTests.java => executor1/Executor1ApplicationTests.java} (70%) diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml index 59d5b2a..850b2e1 100644 --- a/executor-pool/pom.xml +++ b/executor-pool/pom.xml @@ -54,12 +54,6 @@ jakarta.validation jakarta.validation-api - - ch.unisg - tapas-tasks - 0.0.1-SNAPSHOT - compile - javax.transaction javax.transaction-api diff --git a/executor-pool/src/main/java/ch/unisg/common/SelfValidating.java b/executor-pool/src/main/java/ch/unisg/common/SelfValidating.java new file mode 100644 index 0000000..cacf16e --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/common/SelfValidating.java @@ -0,0 +1,30 @@ +package ch.unisg.common; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import java.util.Set; + +public class SelfValidating { + + private Validator validator; + + public SelfValidating() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Evaluates all Bean Validations on the attributes of this + * instance. + */ + protected void validateSelf() { + @SuppressWarnings("unchecked") + Set> violations = validator.validate((T) this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java index 26b495e..2682610 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java @@ -1,7 +1,7 @@ package ch.unisg.executorpool.application.port.in; +import ch.unisg.common.SelfValidating; import ch.unisg.executorpool.domain.ExecutorPool; -import ch.unisg.tapastasks.common.SelfValidating; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java index 509dba5..c812eab 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java @@ -1,7 +1,7 @@ package ch.unisg.executorpool.application.port.in; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; -import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.common.SelfValidating; import lombok.Value; import javax.validation.constraints.NotNull; diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java index a35b9ed..11763a9 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java @@ -1,7 +1,7 @@ package ch.unisg.executorpool.application.port.in; import ch.unisg.executorpool.domain.ExecutorClass; -import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.common.SelfValidating; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; import lombok.Value; diff --git a/executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java b/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java similarity index 70% rename from executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java rename to executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java index 5724a1c..889c9cd 100644 --- a/executor1/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java +++ b/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java @@ -1,10 +1,10 @@ -package ch.unisg.executor2; +package ch.unisg.executor1; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Executor2ApplicationTests { +class Executor1ApplicationTests { @Test void contextLoads() { -- 2.45.1 From 7f76bf43e4f3cdb6bb3582a458786602f6f5b4a3 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 15:53:36 +0200 Subject: [PATCH 28/94] naming fixes --- .github/workflows/build-and-deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 0429aca..60db7ac 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -39,22 +39,22 @@ jobs: - name: Build with Maven run: mvn -f executor-pool/pom.xml --batch-mode --update-snapshots verify - - run: cp ./executor-pool/target/executor-pool-0.0.1.jar ./target + - run: cp ./executor-pool/target/executor-pool-0.0.1-SNAPSHOT.jar ./target - name: Build with Maven run: mvn -f executorBase/pom.xml --batch-mode --update-snapshots install - name: Build with Maven run: mvn -f executor1/pom.xml --batch-mode --update-snapshots verify - - run: cp ./executor1/target/executor1-0.0.1.jar ./target + - run: cp ./executor1/target/executor1-0.0.1-SNAPSHOT.jar ./target - name: Build with Maven run: mvn -f executor2/pom.xml --batch-mode --update-snapshots verify - - run: cp ./executor2/target/executor2-0.0.1.jar ./target + - run: cp ./executor2/target/executor2-0.0.1-SNAPSHOT.jar ./target - name: Build with Maven run: mvn -f tapas-tasks/pom.xml --batch-mode --update-snapshots verify - - run: cp ./tapas-tasks/target/tapas-tasks-0.0.1.jar ./target + - run: cp ./tapas-tasks/target/tapas-tasks-0.0.1-SNAPSHOT.jar ./target - run: cp ./.deployment/docker-compose.yml ./target - name: Archive artifacts -- 2.45.1 From c414a4c1dd18aed8d1c36ae1b884db03367a63c0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 15:57:01 +0200 Subject: [PATCH 29/94] naming fixes --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 60db7ac..2512226 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -42,7 +42,7 @@ jobs: - run: cp ./executor-pool/target/executor-pool-0.0.1-SNAPSHOT.jar ./target - name: Build with Maven - run: mvn -f executorBase/pom.xml --batch-mode --update-snapshots install + run: mvn -f executor-base/pom.xml --batch-mode --update-snapshots install - name: Build with Maven run: mvn -f executor1/pom.xml --batch-mode --update-snapshots verify -- 2.45.1 From 25bf4efefd922d2ab6a9a16abe868a3f55f47e4b Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 16:20:21 +0200 Subject: [PATCH 30/94] fixes? --- .deployment/docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index b8397f3..5aee641 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -43,7 +43,7 @@ services: assignment: image: openjdk - command: "java -jar /data/assignment-0.0.1.jar" + command: "java -jar /data/assignment-0.0.1-SNAPSHOT.jar" restart: unless-stopped volumes: - ./:/data/ @@ -58,7 +58,7 @@ services: executor-pool: image: openjdk - command: "java -jar /data/executor-pool-0.0.1.jar" + command: "java -jar /data/executor-pool-0.0.1-SNAPSHOT.jar" restart: unless-stopped volumes: - ./:/data/ @@ -73,7 +73,7 @@ services: executor1: image: openjdk - command: "java -jar /data/executor1-0.0.1.jar" + command: "java -jar /data/executor1-0.0.1-SNAPSHOT.jar" restart: unless-stopped depends_on: - executor-pool @@ -91,7 +91,7 @@ services: executor2: image: openjdk - command: "java -jar /data/executor2-0.0.1.jar" + command: "java -jar /data/executor2-0.0.1-SNAPSHOT.jar" restart: unless-stopped depends_on: - executor-pool -- 2.45.1 From 0cdaf9b830340023ce4abbdaf8f9809a847167c5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 16:31:02 +0200 Subject: [PATCH 31/94] fixes --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2512226..eea8f1b 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -97,4 +97,4 @@ jobs: touch acme.json sudo chmod 0600 acme.json sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env - sudo docker-compose up -d + sudo docker-compose up -d --force-recreate -- 2.45.1 From e260df0de2542040a880ee12e35b53b1a07ccfd8 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 16:51:28 +0200 Subject: [PATCH 32/94] fixes --- docker-compose.yaml | 4 ++-- tapas-tasks/Dockerfile | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 tapas-tasks/Dockerfile diff --git a/docker-compose.yaml b/docker-compose.yaml index 8c4bfcf..1364b66 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -31,7 +31,7 @@ services: dockerfile: "Dockerfile" target: development ports: - - "8083:8081" + - "8083:8083" - "5007:5005" volumes: - ./executor-pool/src:/opt/app/src @@ -43,7 +43,7 @@ services: dockerfile: "Dockerfile" target: development ports: - - "8084:8081" + - "8084:8084" - "5008:5005" volumes: - ./executor1/src:/opt/app/src diff --git a/tapas-tasks/Dockerfile b/tapas-tasks/Dockerfile deleted file mode 100644 index db90fb6..0000000 --- a/tapas-tasks/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM openjdk:11 AS development - -WORKDIR /opt/app - -# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db - -COPY .mvn/ .mvn -COPY mvnw pom.xml mvnw.cmd ./ - -RUN apt-get clean && apt-get update && apt-get install dos2unix -RUN dos2unix mvnw - -RUN ./mvnw dependency:go-offline - -COPY src /opt/app/src -COPY *target /opt/app/target - -CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] -- 2.45.1 From 174ff9f605ffb46306d2f619b32820ac1d4b5bac Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 17:08:34 +0200 Subject: [PATCH 33/94] deployment fix --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index eea8f1b..3b82a91 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -97,4 +97,4 @@ jobs: touch acme.json sudo chmod 0600 acme.json sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env - sudo docker-compose up -d --force-recreate + sudo docker-compose up -d --build --force-recreate -- 2.45.1 From 86702e319559d5457c1ea594c27243bef0fe40d1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 17 Oct 2021 17:43:55 +0200 Subject: [PATCH 34/94] bugfixes --- executor-pool/pom.xml | 4 ++++ tapas-tasks/Dockerfile | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tapas-tasks/Dockerfile diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml index 850b2e1..2e75dcc 100644 --- a/executor-pool/pom.xml +++ b/executor-pool/pom.xml @@ -30,6 +30,10 @@ runtime true + + org.springframework.boot + spring-boot-starter-validation + org.projectlombok lombok diff --git a/tapas-tasks/Dockerfile b/tapas-tasks/Dockerfile new file mode 100644 index 0000000..db90fb6 --- /dev/null +++ b/tapas-tasks/Dockerfile @@ -0,0 +1,18 @@ +FROM openjdk:11 AS development + +WORKDIR /opt/app + +# ENV SPRING_DATASOURCE_URL=jdbc:mysql://backend-db:3306/db + +COPY .mvn/ .mvn +COPY mvnw pom.xml mvnw.cmd ./ + +RUN apt-get clean && apt-get update && apt-get install dos2unix +RUN dos2unix mvnw + +RUN ./mvnw dependency:go-offline + +COPY src /opt/app/src +COPY *target /opt/app/target + +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"", "-Dspring.devtools.restart.enabled=true"] -- 2.45.1 From 941e4d7f200baa367b9c74ed1150fa3e1469edf7 Mon Sep 17 00:00:00 2001 From: reynisson Date: Mon, 18 Oct 2021 00:26:20 +0200 Subject: [PATCH 35/94] Added the ADRs from last submission. These still need to be revised to reflect more recent design choices --- .adr-dir | 1 + .../0001-record-architecture-decisions.md | 19 +++++++++++++++++ .../0002-seperate-service-for-executors.md | 21 +++++++++++++++++++ ...-seperate-service-for-assignment-domain.md | 21 +++++++++++++++++++ ...0004-seperate-service-for-executor-pool.md | 21 +++++++++++++++++++ .../0005-event-driven-communication.md | 20 ++++++++++++++++++ ...al-database-or-one-database-per-service.md | 20 ++++++++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 .adr-dir create mode 100644 doc/architecture/decisions/0001-record-architecture-decisions.md create mode 100644 doc/architecture/decisions/0002-seperate-service-for-executors.md create mode 100644 doc/architecture/decisions/0003-seperate-service-for-assignment-domain.md create mode 100644 doc/architecture/decisions/0004-seperate-service-for-executor-pool.md create mode 100644 doc/architecture/decisions/0005-event-driven-communication.md create mode 100644 doc/architecture/decisions/0006-one-global-database-or-one-database-per-service.md diff --git a/.adr-dir b/.adr-dir new file mode 100644 index 0000000..0d38988 --- /dev/null +++ b/.adr-dir @@ -0,0 +1 @@ +doc/architecture/decisions diff --git a/doc/architecture/decisions/0001-record-architecture-decisions.md b/doc/architecture/decisions/0001-record-architecture-decisions.md new file mode 100644 index 0000000..ed632e4 --- /dev/null +++ b/doc/architecture/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,19 @@ +# 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). diff --git a/doc/architecture/decisions/0002-seperate-service-for-executors.md b/doc/architecture/decisions/0002-seperate-service-for-executors.md new file mode 100644 index 0000000..781d9b4 --- /dev/null +++ b/doc/architecture/decisions/0002-seperate-service-for-executors.md @@ -0,0 +1,21 @@ +# 2. Seperate service for Executors + +Date: 2021-10-18 + +## Status + +Accepted + +## Context + +The users need to be able to add new executors to the executor pool. The functionality of the executor is currently unknown. + +## Decision + +We will use a separate microservice for each executor. +New executors will be added/removed during runtime. Therefore, we need a high extensibility. +Different executors can have different execution times and a different load. This means the executors scale differently. + +## Consequences + +Having executors as its own service we can deploy new executors independently and easily add new executors during runtime and guarantee high scalability as well as evolvability. diff --git a/doc/architecture/decisions/0003-seperate-service-for-assignment-domain.md b/doc/architecture/decisions/0003-seperate-service-for-assignment-domain.md new file mode 100644 index 0000000..309c793 --- /dev/null +++ b/doc/architecture/decisions/0003-seperate-service-for-assignment-domain.md @@ -0,0 +1,21 @@ +# 3. Seperate service for assignment domain + +Date: 2021-10-18 + +## Status + +Accepted + +## Context + +The Assignment Service handles the assignment of a task to a corresponding and available executor. It keeps track of all the connections between tasks and executors. + +## Decision + +The assignment domain will be its own service. +The assignment service will be a central point in our application. It will have most of the business logic in it and will communicate with all the different services. Therefore, other services can be kind of “dumb” and only need to focus on their simple tasks. +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 we 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. diff --git a/doc/architecture/decisions/0004-seperate-service-for-executor-pool.md b/doc/architecture/decisions/0004-seperate-service-for-executor-pool.md new file mode 100644 index 0000000..6c8aea2 --- /dev/null +++ b/doc/architecture/decisions/0004-seperate-service-for-executor-pool.md @@ -0,0 +1,21 @@ +# 4. Seperate service for executor pool + +Date: 2021-10-18 + +## Status + +Accepted + +## Context + +The Executor pool keeps track of the connected executors and their purpose and status. + +## Decision + +We will have a separate service for the executor pool. +There are no other domains which share the same or similar functionality. +The executor pool also scales differently than other services. + +## Consequences + +Having the executor pool as a separate service will help with the deployability of this service but will make the overall structure more complex and reduces testability. diff --git a/doc/architecture/decisions/0005-event-driven-communication.md b/doc/architecture/decisions/0005-event-driven-communication.md new file mode 100644 index 0000000..c711f62 --- /dev/null +++ b/doc/architecture/decisions/0005-event-driven-communication.md @@ -0,0 +1,20 @@ +# 5. Event driven communication + +Date: 2021-10-18 + +## Status + +Accepted + +## 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. diff --git a/doc/architecture/decisions/0006-one-global-database-or-one-database-per-service.md b/doc/architecture/decisions/0006-one-global-database-or-one-database-per-service.md new file mode 100644 index 0000000..5f5c70d --- /dev/null +++ b/doc/architecture/decisions/0006-one-global-database-or-one-database-per-service.md @@ -0,0 +1,20 @@ +# 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 don’t 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 don’t need to know about each other. To guarantee data consistency we can also use a pattern like sagas. -- 2.45.1 From d08a6d0b673e2aea50d338e10ae7cb3b72d39726 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 18 Oct 2021 11:45:52 +0200 Subject: [PATCH 36/94] merge everything from main project --- .deployment/docker-compose.yml | 15 + .github/workflows/build-and-deploy.yml | 4 + tapas-auction-house/.editorconfig | 9 + tapas-auction-house/.gitignore | 33 ++ .../.mvn/wrapper/MavenWrapperDownloader.java | 117 +++++++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + tapas-auction-house/README.md | 101 ++++++ tapas-auction-house/mvnw | 310 ++++++++++++++++++ tapas-auction-house/mvnw.cmd | 182 ++++++++++ tapas-auction-house/pom.xml | 80 +++++ .../tapas/TapasAuctionHouseApplication.java | 71 ++++ .../common/clients/TapasMqttClient.java | 94 ++++++ .../common/clients/WebSubSubscriber.java | 28 ++ .../formats/AuctionJsonRepresentation.java | 60 ++++ ...ExecutorAddedEventListenerHttpAdapter.java | 34 ++ ...ecutorRemovedEventListenerHttpAdapter.java | 16 + .../mqtt/AuctionEventMqttListener.java | 11 + .../mqtt/AuctionEventsMqttDispatcher.java | 51 +++ ...ExecutorAddedEventListenerMqttAdapter.java | 48 +++ ...tionStartedEventListenerWebSubAdapter.java | 18 + .../in/web/LaunchAuctionWebController.java | 72 ++++ .../RetrieveOpenAuctionsWebController.java | 58 ++++ ...blishAuctionStartedEventWebSubAdapter.java | 37 +++ .../out/web/AuctionWonEventHttpAdapter.java | 20 ++ .../PlaceBidForAuctionCommandHttpAdapter.java | 19 ++ .../handler/AuctionStartedHandler.java | 59 ++++ .../handler/ExecutorAddedHandler.java | 16 + .../handler/ExecutorRemovedHandler.java | 19 ++ .../port/in/AuctionStartedEvent.java | 21 ++ .../port/in/AuctionStartedEventHandler.java | 6 + .../port/in/ExecutorAddedEvent.java | 32 ++ .../port/in/ExecutorAddedEventHandler.java | 6 + .../port/in/ExecutorRemovedEvent.java | 26 ++ .../port/in/ExecutorRemovedEventHandler.java | 6 + .../port/in/LaunchAuctionCommand.java | 37 +++ .../port/in/LaunchAuctionUseCase.java | 8 + .../port/in/RetrieveOpenAuctionsQuery.java | 7 + .../port/in/RetrieveOpenAuctionsUseCase.java | 10 + .../port/out/AuctionStartedEventPort.java | 11 + .../port/out/AuctionWonEventPort.java | 11 + .../port/out/PlaceBidForAuctionCommand.java | 25 ++ .../out/PlaceBidForAuctionCommandPort.java | 6 + .../service/RetrieveOpenAuctionsService.java | 22 ++ .../service/StartAuctionService.java | 113 +++++++ .../tapas/auctionhouse/domain/Auction.java | 171 ++++++++++ .../auctionhouse/domain/AuctionRegistry.java | 105 ++++++ .../domain/AuctionStartedEvent.java | 15 + .../auctionhouse/domain/AuctionWonEvent.java | 16 + .../unisg/tapas/auctionhouse/domain/Bid.java | 66 ++++ .../auctionhouse/domain/ExecutorRegistry.java | 86 +++++ .../common/AuctionHouseResourceDirectory.java | 57 ++++ .../unisg/tapas/common/ConfigProperties.java | 64 ++++ .../ch/unisg/tapas/common/SelfValidating.java | 25 ++ .../src/main/resources/application.properties | 8 + .../TapasAuctionHouseApplicationTests.java | 13 + tapas-tasks/README.md | 152 +++++++-- tapas-tasks/pom.xml | 16 +- .../tapastasks/TapasTasksApplication.java | 7 +- .../formats/TaskJsonPatchRepresentation.java | 102 ++++++ .../in/formats/TaskJsonRepresentation.java | 115 +++++++ .../in/messaging/UnknownEventException.java | 3 + .../TaskAssignedEventListenerHttpAdapter.java | 39 +++ .../http/TaskEventHttpDispatcher.java | 103 ++++++ .../in/messaging/http/TaskEventListener.java | 24 ++ .../TaskExecutedEventListenerHttpAdapter.java | 34 ++ .../TaskStartedEventListenerHttpAdapter.java | 32 ++ .../AddNewTaskToTaskListWebController.java | 58 +++- ...RetrieveTaskFromTaskListWebController.java | 33 +- .../handler/TaskAssignedHandler.java | 19 ++ .../handler/TaskExecutedHandler.java | 19 ++ .../handler/TaskStartedHandler.java | 19 ++ .../port/in/AddNewTaskToTaskListCommand.java | 17 +- .../in/RetrieveTaskFromTaskListUseCase.java | 2 +- .../port/in/TaskAssignedEvent.java | 25 ++ .../port/in/TaskAssignedEventHandler.java | 8 + .../port/in/TaskExecutedEvent.java | 34 ++ .../port/in/TaskExecutedEventHandler.java | 8 + .../application/port/in/TaskStartedEvent.java | 28 ++ .../port/in/TaskStartedEventHandler.java | 8 + .../service/AddNewTaskToTaskListService.java | 8 +- .../RetrieveTaskFromTaskListService.java | 4 +- .../unisg/tapastasks/tasks/domain/Task.java | 68 +++- .../tapastasks/tasks/domain/TaskList.java | 60 +++- .../tasks/domain/TaskNotFoundException.java | 3 + .../src/main/resources/application.properties | 1 + 86 files changed, 3527 insertions(+), 79 deletions(-) create mode 100644 tapas-auction-house/.editorconfig create mode 100644 tapas-auction-house/.gitignore create mode 100644 tapas-auction-house/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 tapas-auction-house/.mvn/wrapper/maven-wrapper.jar create mode 100644 tapas-auction-house/.mvn/wrapper/maven-wrapper.properties create mode 100644 tapas-auction-house/README.md create mode 100755 tapas-auction-house/mvnw create mode 100644 tapas-auction-house/mvnw.cmd create mode 100644 tapas-auction-house/pom.xml create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventMqttListener.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/LaunchAuctionWebController.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/RetrieveOpenAuctionsWebController.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/PlaceBidForAuctionCommandHttpAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/AuctionStartedHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEventHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEventHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEventHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionUseCase.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsQuery.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsUseCase.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionStartedEventPort.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionWonEventPort.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommand.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommandPort.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/RetrieveOpenAuctionsService.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionRegistry.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionStartedEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionWonEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Bid.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/common/AuctionHouseResourceDirectory.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/common/SelfValidating.java create mode 100644 tapas-auction-house/src/main/resources/application.properties create mode 100644 tapas-auction-house/src/test/java/ch/unisg/tapas/TapasAuctionHouseApplicationTests.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonPatchRepresentation.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/UnknownEventException.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskAssignedEventListenerHttpAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventHttpDispatcher.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventListener.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskExecutedEventListenerHttpAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskStartedEventListenerHttpAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskAssignedHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskExecutedHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskStartedHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEvent.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEventHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEventHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEvent.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEventHandler.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskNotFoundException.java diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 5aee641..01a5a77 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -41,6 +41,21 @@ services: - "traefik.http.routers.tapas-tasks.entryPoints=web,websecure" - "traefik.http.routers.tapas-tasks.tls.certresolver=le" + tapas-auction-house: + image: openjdk + command: "java -jar /data/tapas-auction-house-0.0.1-SNAPSHOT.jar" + restart: unless-stopped + volumes: + - ./:/data/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.tapas-auction-house.rule=Host(`tapas-auction-house.${PUB_IP}.nip.io`)" + - "traefik.http.routers.tapas-auction-house.service=tapas-auction-house" + - "traefik.http.services.tapas-auction-house.loadbalancer.server.port=8086" + - "traefik.http.routers.tapas-auction-house.tls=true" + - "traefik.http.routers.tapas-auction-house.entryPoints=web,websecure" + - "traefik.http.routers.tapas-auction-house.tls.certresolver=le" + assignment: image: openjdk command: "java -jar /data/assignment-0.0.1-SNAPSHOT.jar" diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 3b82a91..d223887 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -56,6 +56,10 @@ jobs: 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 + - name: Build with Maven + run: mvn -f tapas-auction-house/pom.xml --batch-mode --update-snapshots verify + - run: cp ./tapas-auction-house/target/tapas-auction-house-0.0.1-SNAPSHOT.jar ./target + - run: cp ./.deployment/docker-compose.yml ./target - name: Archive artifacts uses: actions/upload-artifact@v1 diff --git a/tapas-auction-house/.editorconfig b/tapas-auction-house/.editorconfig new file mode 100644 index 0000000..c4f3e5b --- /dev/null +++ b/tapas-auction-house/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true diff --git a/tapas-auction-house/.gitignore b/tapas-auction-house/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/tapas-auction-house/.gitignore @@ -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/ diff --git a/tapas-auction-house/.mvn/wrapper/MavenWrapperDownloader.java b/tapas-auction-house/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/tapas-auction-house/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/tapas-auction-house/.mvn/wrapper/maven-wrapper.jar b/tapas-auction-house/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/tapas-auction-house/.mvn/wrapper/maven-wrapper.properties b/tapas-auction-house/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ffdc10e --- /dev/null +++ b/tapas-auction-house/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/tapas-auction-house/README.md b/tapas-auction-house/README.md new file mode 100644 index 0000000..683500d --- /dev/null +++ b/tapas-auction-house/README.md @@ -0,0 +1,101 @@ +# tapas-auction-house + +The Auction House is the part of your TAPAS application that is largely responsible for the interactions +with the TAPAS applications developed by the other groups. More precisely, it is responsible for +launching and managing auctions and it is implemented following the Hexagonal Architecture (based on +examples from book "Get Your Hands Dirty on Clean Architecture" by Tom Hombergs). + +Technologies: Spring Boot, Maven + +**Note:** this repository contains an [EditorConfig](https://editorconfig.org/) file (`.editorconfig`) +with default editor settings. EditorConfig is supported out-of-the-box by the IntelliJ IDE. To help maintain +consistent code styles, we recommend to reuse this editor configuration file in all your services. + +## Project Overview + +This project provides a partial implementation of the Auction House. The code is documented in detail, +here we only include a summary of implemented features: +* running and managing auctions: + * each auction has a deadline by which it is open for bids + * once the deadline has passed, the auction house closes the auction and selects a random bid +* starting an auction using a command via an HTTP adapter (see sample request below) +* retrieving the list of open auctions via an HTTP adapter, i.e. auctions accepting bids (see sample + request below) +* receiving events when executors are added to the TAPAS application (both via HTTP and MQTT adapters) +* the logic for automatic placement of bids in auctions: the auction house will place a bid in every + auction for which there is at least one executor that can handle the type of task + being auctioned +* discovery of auction houses via a provided resource directory (see assignment sheet for + Exercises 5 & 6 for more details) + +## Overview of Adapters + +In addition to the overall skeleton of the auction house, the current partial implementation provides +several adapters to help you get started. + +### HTTP Adapters + +Sample HTTP request for launching an auction: + +```shell +curl -i --location --request POST 'http://localhost:8083/auctions/' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "taskUri" : "http://example.org", + "taskType" : "taskType1", + "deadline" : 10000 +}' + +HTTP/1.1 201 +Content-Type: application/json +Content-Length: 131 +Date: Sun, 17 Oct 2021 22:34:13 GMT + +{ + "auctionId":"1", + "auctionHouseUri":"http://localhost:8083/", + "taskUri":"http://example.org", + "taskType":"taskType1", + "deadline":10000 +} +``` + +Sample HTTP request for retrieving auctions currently open for bids: + +```shell +curl -i --location --request GET 'http://localhost:8083/auctions/' + +HTTP/1.1 200 +Content-Type: application/json +Content-Length: 133 +Date: Sun, 17 Oct 2021 22:34:20 GMT + +[ + { + "auctionId":"1", + "auctionHouseUri":"http://localhost:8083/", + "taskUri":"http://example.org", + "taskType":"taskType1", + "deadline":10000 + } +] +``` + +Sending an [ExecutorAddedEvent](src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java) +via an HTTP request: + +```shell +curl -i --location --request POST 'http://localhost:8083/executors/taskType1/executor1' + +HTTP/1.1 204 +Date: Sun, 17 Oct 2021 22:38:45 GMT +``` + +### MQTT Adapters + +Sending an [ExecutorAddedEvent](src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java) +via an MQTT message via HiveMQ's [MQTT CLI](https://hivemq.github.io/mqtt-cli/): + +```shell + mqtt pub -t ch/unisg/tapas-group1/executors -m '{ "taskType" : "taskType1", "executorId" : "executor1" }' +``` diff --git a/tapas-auction-house/mvnw b/tapas-auction-house/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/tapas-auction-house/mvnw @@ -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 "$@" diff --git a/tapas-auction-house/mvnw.cmd b/tapas-auction-house/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/tapas-auction-house/mvnw.cmd @@ -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% diff --git a/tapas-auction-house/pom.xml b/tapas-auction-house/pom.xml new file mode 100644 index 0000000..4b9cbb6 --- /dev/null +++ b/tapas-auction-house/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.3 + + + ch.unisg + tapas-auction-house + 0.0.1-SNAPSHOT + tapas-auction-house + TAPAS Auction House + + 11 + + + + Eclipse Paho Repo + https://repo.eclipse.org/content/repositories/paho-releases/ + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.0 + + + javax.transaction + javax.transaction-api + 1.2 + + + javax.validation + validation-api + 1.1.0.Final + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java new file mode 100644 index 0000000..8fc22d0 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -0,0 +1,71 @@ +package ch.unisg.tapas; + +import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient; +import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; +import ch.unisg.tapas.auctionhouse.adapter.common.clients.WebSubSubscriber; +import ch.unisg.tapas.common.AuctionHouseResourceDirectory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.net.URI; +import java.util.List; + +/** + * Main TAPAS Auction House application. + */ +@SpringBootApplication +public class TapasAuctionHouseApplication { + private static final Logger LOGGER = LogManager.getLogger(TapasAuctionHouseApplication.class); + + public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; + public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + + public static void main(String[] args) { + SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); + + // We will use these bootstrap methods in Week 6: + // bootstrapMarketplaceWithWebSub(); + // bootstrapMarketplaceWithMqtt(); + + tapasAuctioneerApp.run(args); + } + + /** + * Discovers auction houses and subscribes to WebSub notifications + */ + private static void bootstrapMarketplaceWithWebSub() { + List auctionHouseEndpoints = discoverAuctionHouseEndpoints(); + LOGGER.info("Found auction house endpoints: " + auctionHouseEndpoints); + + WebSubSubscriber subscriber = new WebSubSubscriber(); + + for (String endpoint : auctionHouseEndpoints) { + subscriber.subscribeToAuctionHouseEndpoint(URI.create(endpoint)); + } + } + + /** + * Connects to an MQTT broker, presumably the one used by all TAPAS groups to communicate with + * one another + */ + private static void bootstrapMarketplaceWithMqtt() { + try { + AuctionEventsMqttDispatcher dispatcher = new AuctionEventsMqttDispatcher(); + TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + client.startReceivingMessages(); + } catch (MqttException e) { + LOGGER.error(e.getMessage(), e); + } + } + + private static List discoverAuctionHouseEndpoints() { + AuctionHouseResourceDirectory rd = new AuctionHouseResourceDirectory( + URI.create(RESOURCE_DIRECTORY) + ); + + return rd.retrieveAuctionHouseEndpoints(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java new file mode 100644 index 0000000..708d512 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java @@ -0,0 +1,94 @@ +package ch.unisg.tapas.auctionhouse.adapter.common.clients; + +import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +/** + * MQTT client for your TAPAS application. This class is defined as a singleton, but it does not have + * to be this way. This class is only provided as an example to help you bootstrap your project. + * You are welcomed to change this class as you see fit. + */ +public class TapasMqttClient { + private static final Logger LOGGER = LogManager.getLogger(TapasMqttClient.class); + + private static TapasMqttClient tapasClient = null; + + private MqttClient mqttClient; + private final String mqttClientId; + private final String brokerAddress; + + private final MessageReceivedCallback messageReceivedCallback; + + private final AuctionEventsMqttDispatcher dispatcher; + + private TapasMqttClient(String brokerAddress, AuctionEventsMqttDispatcher dispatcher) { + this.mqttClientId = UUID.randomUUID().toString(); + this.brokerAddress = brokerAddress; + + this.messageReceivedCallback = new MessageReceivedCallback(); + + this.dispatcher = dispatcher; + } + + public static synchronized TapasMqttClient getInstance(String brokerAddress, + AuctionEventsMqttDispatcher dispatcher) { + + if (tapasClient == null) { + tapasClient = new TapasMqttClient(brokerAddress, dispatcher); + } + + return tapasClient; + } + + public void startReceivingMessages() throws MqttException { + mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence()); + mqttClient.connect(); + mqttClient.setCallback(messageReceivedCallback); + + subscribeToAllTopics(); + } + + public void stopReceivingMessages() throws MqttException { + mqttClient.disconnect(); + } + + private void subscribeToAllTopics() throws MqttException { + for (String topic : dispatcher.getAllTopics()) { + subscribeToTopic(topic); + } + } + + private void subscribeToTopic(String topic) throws MqttException { + mqttClient.subscribe(topic); + } + + private void publishMessage(String topic, String payload) throws MqttException { + MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); + mqttClient.publish(topic, message); + } + + private class MessageReceivedCallback implements MqttCallback { + + @Override + public void connectionLost(Throwable cause) { } + + @Override + public void messageArrived(String topic, MqttMessage message) { + LOGGER.info("Received new MQTT message for topic " + topic + ": " + + new String(message.getPayload())); + + if (topic != null && !topic.isEmpty()) { + dispatcher.dispatchEvent(topic, message); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { } + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java new file mode 100644 index 0000000..da2b096 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java @@ -0,0 +1,28 @@ +package ch.unisg.tapas.auctionhouse.adapter.common.clients; + +import java.net.URI; + +/** + * Subscribes to the WebSub hubs of auction houses discovered at run time. This class is instantiated + * from {@link ch.unisg.tapas.TapasAuctionHouseApplication} when boostraping the TAPAS marketplace + * via WebSub. + */ +public class WebSubSubscriber { + + public void subscribeToAuctionHouseEndpoint(URI endpoint) { + // TODO Subscribe to the auction house endpoint via WebSub: + // 1. Send a request to the auction house in order to discover the WebSub hub to subscribe to. + // The request URI should depend on the design of the Auction House HTTP API. + // 2. Send a subscription request to the discovered WebSub hub to subscribe to events relevant + // for this auction house. + // 3. Handle the validation of intent from the WebSub hub (see WebSub protocol). + // + // Once the subscription is activated, the hub will send "fat pings" with content updates. + // The content received from the hub will depend primarily on the design of the Auction House + // HTTP API. + // + // For further details see: + // - W3C WebSub Recommendation: https://www.w3.org/TR/websub/ + // - the implementation notes of the WebSub hub you are using to distribute events + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java new file mode 100644 index 0000000..4500423 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java @@ -0,0 +1,60 @@ +package ch.unisg.tapas.auctionhouse.adapter.common.formats; + +import ch.unisg.tapas.auctionhouse.domain.Auction; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +/** + * Used to expose a representation of the state of an auction through an interface. This class is + * only meant as a starting point when defining a uniform HTTP API for the Auction House: feel free + * to modify this class as you see fit! + */ +public class AuctionJsonRepresentation { + public static final String MEDIA_TYPE = "application/json"; + + @Getter @Setter + private String auctionId; + + @Getter @Setter + private String auctionHouseUri; + + @Getter @Setter + private String taskUri; + + @Getter @Setter + private String taskType; + + @Getter @Setter + private Integer deadline; + + public AuctionJsonRepresentation() { } + + public AuctionJsonRepresentation(String auctionId, String auctionHouseUri, String taskUri, + String taskType, Integer deadline) { + this.auctionId = auctionId; + this.auctionHouseUri = auctionHouseUri; + this.taskUri = taskUri; + this.taskType = taskType; + this.deadline = deadline; + } + + public AuctionJsonRepresentation(Auction auction) { + this.auctionId = auction.getAuctionId().getValue(); + this.auctionHouseUri = auction.getAuctionHouseUri().getValue().toString(); + this.taskUri = auction.getTaskUri().getValue().toString(); + this.taskType = auction.getTaskType().getValue(); + this.deadline = auction.getDeadline().getValue(); + } + + public static String serialize(Auction auction) throws JsonProcessingException { + AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return mapper.writeValueAsString(representation); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java new file mode 100644 index 0000000..3511b7d --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java @@ -0,0 +1,34 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http; + +import ch.unisg.tapas.auctionhouse.application.handler.ExecutorAddedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Template for receiving an executor added event via HTTP + */ +@RestController +public class ExecutorAddedEventListenerHttpAdapter { + + @PostMapping(path = "/executors/{taskType}/{executorId}") + public ResponseEntity handleExecutorAddedEvent(@PathVariable("taskType") String taskType, + @PathVariable("executorId") String executorId) { + + ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( + new ExecutorRegistry.ExecutorIdentifier(executorId), + new Auction.AuctionedTaskType(taskType) + ); + + ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler(); + newExecutorHandler.handleNewExecutorEvent(executorAddedEvent); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java new file mode 100644 index 0000000..53811f9 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java @@ -0,0 +1,16 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http; + +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * Template for handling an executor removed event received via an HTTP request + */ +@RestController +public class ExecutorRemovedEventListenerHttpAdapter { + + // TODO: add annotations for request method, request URI, etc. + public void handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) { + // TODO: implement logic + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventMqttListener.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventMqttListener.java new file mode 100644 index 0000000..6da39e6 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventMqttListener.java @@ -0,0 +1,11 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + * Abstract MQTT listener for auction-related events + */ +public abstract class AuctionEventMqttListener { + + public abstract boolean handleEvent(MqttMessage message); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java new file mode 100644 index 0000000..e5eaf12 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java @@ -0,0 +1,51 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import org.eclipse.paho.client.mqttv3.*; + +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; + +/** + * Dispatches MQTT messages for known topics to associated event listeners. Used in conjunction with + * {@link ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient}. + * + * This is where you would define MQTT topics and map them to event listeners (see + * {@link AuctionEventsMqttDispatcher#initRouter()}). + * + * This class is only provided as an example to help you bootstrap the project. You are welcomed to + * change this class as you see fit. + */ +public class AuctionEventsMqttDispatcher { + private final Map router; + + public AuctionEventsMqttDispatcher() { + this.router = new Hashtable<>(); + initRouter(); + } + + // TODO: Register here your topics and event listener adapters + private void initRouter() { + router.put("ch/unisg/tapas-group-tutors/executors", new ExecutorAddedEventListenerMqttAdapter()); + } + + /** + * Returns all topics registered with this dispatcher. + * + * @return the set of registered topics + */ + public Set getAllTopics() { + return router.keySet(); + } + + /** + * Dispatches an event received via MQTT for a given topic. + * + * @param topic the topic for which the MQTT message was received + * @param message the received MQTT message + */ + public void dispatchEvent(String topic, MqttMessage message) { + AuctionEventMqttListener listener = router.get(topic); + listener.handleEvent(message); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java new file mode 100644 index 0000000..2f661d1 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java @@ -0,0 +1,48 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import ch.unisg.tapas.auctionhouse.application.handler.ExecutorAddedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + * Listener that handles events when an executor was added to this TAPAS application. + * + * This class is only provided as an example to help you bootstrap the project. + */ +public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListener { + private static final Logger LOGGER = LogManager.getLogger(ExecutorAddedEventListenerMqttAdapter.class); + + @Override + public boolean handleEvent(MqttMessage message) { + String payload = new String(message.getPayload()); + + try { + // Note: this messge representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String taskType = data.get("taskType").asText(); + String executorId = data.get("executorId").asText(); + + ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( + new ExecutorRegistry.ExecutorIdentifier(executorId), + new Auction.AuctionedTaskType(taskType) + ); + + ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler(); + newExecutorHandler.handleNewExecutorEvent(executorAddedEvent); + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + + return true; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java new file mode 100644 index 0000000..d156452 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java @@ -0,0 +1,18 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub; + +import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler; +import org.springframework.web.bind.annotation.*; + +/** + * This class is a template for handling auction started events received via WebSub + */ +@RestController +public class AuctionStartedEventListenerWebSubAdapter { + private final AuctionStartedHandler auctionStartedHandler; + + public AuctionStartedEventListenerWebSubAdapter(AuctionStartedHandler auctionStartedHandler) { + this.auctionStartedHandler = auctionStartedHandler; + } + + //TODO +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/LaunchAuctionWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/LaunchAuctionWebController.java new file mode 100644 index 0000000..c65631e --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/LaunchAuctionWebController.java @@ -0,0 +1,72 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.web; + +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; +import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionCommand; +import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.net.URI; + +/** + * Controller that handles HTTP requests for launching auctions. This controller implements the + * {@link LaunchAuctionUseCase} use case using the {@link LaunchAuctionCommand}. + */ +@RestController +public class LaunchAuctionWebController { + private final LaunchAuctionUseCase launchAuctionUseCase; + + /** + * Constructs the controller. + * + * @param launchAuctionUseCase an implementation of the launch auction use case + */ + public LaunchAuctionWebController(LaunchAuctionUseCase launchAuctionUseCase) { + this.launchAuctionUseCase = launchAuctionUseCase; + } + + /** + * Handles HTTP POST requests for launching auctions. Note: you are free to modify this handler + * as you see fit to reflect the discussions for the uniform HTTP API for the auction house. + * You should also ensure that this handler has the exact behavior you would expect from the + * defined uniform HTTP API (status codes, returned payload, HTTP headers, etc.) + * + * @param payload a representation of the auction to be launched + * @return + */ + @PostMapping(path = "/auctions/", consumes = AuctionJsonRepresentation.MEDIA_TYPE) + public ResponseEntity launchAuction(@RequestBody AuctionJsonRepresentation payload) { + Auction.AuctionDeadline deadline = (payload.getDeadline() == null) ? + null : new Auction.AuctionDeadline(payload.getDeadline()); + + LaunchAuctionCommand command = new LaunchAuctionCommand( + new Auction.AuctionedTaskUri(URI.create(payload.getTaskUri())), + new Auction.AuctionedTaskType(payload.getTaskType()), + deadline + ); + + // This command returns the created auction. We need the created auction to be able to + // include a representation of it in the HTTP response. + Auction auction = launchAuctionUseCase.launchAuction(command); + + try { + AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction); + String auctionJson = AuctionJsonRepresentation.serialize(auction); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, AuctionJsonRepresentation.MEDIA_TYPE); + + // Return a 201 Created status code and a representation of the created auction + return new ResponseEntity<>(auctionJson, responseHeaders, HttpStatus.CREATED); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/RetrieveOpenAuctionsWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/RetrieveOpenAuctionsWebController.java new file mode 100644 index 0000000..c96a919 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/RetrieveOpenAuctionsWebController.java @@ -0,0 +1,58 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.web; + +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; +import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsQuery; +import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsUseCase; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +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.RestController; + +import java.util.Collection; + +/** + * Controller that handles HTTP requests for retrieving auctions hosted by this auction house that + * are open for bids. This controller implements the {@link RetrieveOpenAuctionsUseCase} use case + * using the {@link RetrieveOpenAuctionsQuery}. + */ +@RestController +public class RetrieveOpenAuctionsWebController { + private final RetrieveOpenAuctionsUseCase retrieveAuctionListUseCase; + + public RetrieveOpenAuctionsWebController(RetrieveOpenAuctionsUseCase retrieveAuctionListUseCase) { + this.retrieveAuctionListUseCase = retrieveAuctionListUseCase; + } + + /** + * Handles HTTP GET requests to retrieve the auctions that are open. Note: you are free to modify + * this handler as you see fit to reflect the discussions for the uniform HTTP API for the + * auction house. You should also ensure that this handler has the exact behavior you would expect + * from the defined uniform HTTP API (status codes, returned payload, HTTP headers, etc.). + * + * @return a representation of a collection with the auctions that are open for bids + */ + @GetMapping(path = "/auctions/") + public ResponseEntity retrieveOpenAuctions() { + Collection auctions = + retrieveAuctionListUseCase.retrieveAuctions(new RetrieveOpenAuctionsQuery()); + + ObjectMapper mapper = new ObjectMapper(); + ArrayNode array = mapper.createArrayNode(); + + for (Auction auction : auctions) { + AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction); + JsonNode node = mapper.valueToTree(representation); + array.add(node); + } + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, "application/json"); + + return new ResponseEntity<>(array.toString(), responseHeaders, HttpStatus.OK); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java new file mode 100644 index 0000000..9e6ec67 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java @@ -0,0 +1,37 @@ +package ch.unisg.tapas.auctionhouse.adapter.out.messaging.websub; + +import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent; +import ch.unisg.tapas.common.ConfigProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * This class is a template for publishing auction started events via WebSub. + */ +@Component +@Primary +public class PublishAuctionStartedEventWebSubAdapter implements AuctionStartedEventPort { + // You can use this object to retrieve properties from application.properties, e.g. the + // WebSub hub publish endpoint, etc. + @Autowired + private ConfigProperties config; + + @Override + public void publishAuctionStartedEvent(AuctionStartedEvent event) { + // TODO + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java new file mode 100644 index 0000000..26949f2 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java @@ -0,0 +1,20 @@ +package ch.unisg.tapas.auctionhouse.adapter.out.web; + +import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort; +import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +/** + * This class is a template for sending auction won events via HTTP. This class was created here only + * as a placeholder, it is up to you to decide how such events should be sent (e.g., via HTTP, + * WebSub, etc.). + */ +@Component +@Primary +public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { + @Override + public void publishAuctionWonEvent(AuctionWonEvent event) { + + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/PlaceBidForAuctionCommandHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/PlaceBidForAuctionCommandHttpAdapter.java new file mode 100644 index 0000000..6db8c68 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/PlaceBidForAuctionCommandHttpAdapter.java @@ -0,0 +1,19 @@ +package ch.unisg.tapas.auctionhouse.adapter.out.web; + +import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommand; +import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommandPort; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +/** + * This class is a tempalte for implementing a place bid for auction command via HTTP. + */ +@Component +@Primary +public class PlaceBidForAuctionCommandHttpAdapter implements PlaceBidForAuctionCommandPort { + + @Override + public void placeBid(PlaceBidForAuctionCommand command) { + // TODO + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/AuctionStartedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/AuctionStartedHandler.java new file mode 100644 index 0000000..e4b312f --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/AuctionStartedHandler.java @@ -0,0 +1,59 @@ +package ch.unisg.tapas.auctionhouse.application.handler; + +import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEvent; +import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEventHandler; +import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommand; +import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommandPort; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Bid; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import ch.unisg.tapas.common.ConfigProperties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Handler for auction started events. This handler will automatically bid in any auction for a + * task of known type, i.e. a task for which the auction house knows an executor is available. + */ +@Component +public class AuctionStartedHandler implements AuctionStartedEventHandler { + private static final Logger LOGGER = LogManager.getLogger(AuctionStartedHandler.class); + + @Autowired + private ConfigProperties config; + + @Autowired + private PlaceBidForAuctionCommandPort placeBidForAuctionCommandPort; + + /** + * Handles an auction started event and bids in all auctions for tasks of known types. + * + * @param auctionStartedEvent the auction started domain event + * @return true unless a runtime exception occurs + */ + @Override + public boolean handleAuctionStartedEvent(AuctionStartedEvent auctionStartedEvent) { + Auction auction = auctionStartedEvent.getAuction(); + + if (ExecutorRegistry.getInstance().containsTaskType(auction.getTaskType())) { + LOGGER.info("Placing bid for task " + auction.getTaskUri() + " of type " + + auction.getTaskType() + " in auction " + auction.getAuctionId() + + " from auction house " + auction.getAuctionHouseUri().getValue().toString()); + + Bid bid = new Bid(auction.getAuctionId(), + new Bid.BidderName(config.getGroupName()), + new Bid.BidderAuctionHouseUri(config.getAuctionHouseUri()), + new Bid.BidderTaskListUri(config.getTaskListUri()) + ); + + PlaceBidForAuctionCommand command = new PlaceBidForAuctionCommand(auction, bid); + placeBidForAuctionCommandPort.placeBid(command); + } else { + LOGGER.info("Cannot execute this task type: " + auction.getTaskType().getValue()); + } + + return true; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java new file mode 100644 index 0000000..624e669 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java @@ -0,0 +1,16 @@ +package ch.unisg.tapas.auctionhouse.application.handler; + +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEventHandler; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import org.springframework.stereotype.Component; + +@Component +public class ExecutorAddedHandler implements ExecutorAddedEventHandler { + + @Override + public boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent) { + return ExecutorRegistry.getInstance().addExecutor(executorAddedEvent.getTaskType(), + executorAddedEvent.getExecutorId()); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java new file mode 100644 index 0000000..c3bfed8 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java @@ -0,0 +1,19 @@ +package ch.unisg.tapas.auctionhouse.application.handler; + +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEvent; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEventHandler; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import org.springframework.stereotype.Component; + +/** + * Handler for executor removed events. It removes the executor from this auction house's executor + * registry. + */ +@Component +public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler { + + @Override + public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) { + return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorId()); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEvent.java new file mode 100644 index 0000000..b937e26 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEvent.java @@ -0,0 +1,21 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +/** + * Event that notifies this auction house that an auction was started by another auction house. + */ +@Value +public class AuctionStartedEvent extends SelfValidating { + @NotNull + private final Auction auction; + + public AuctionStartedEvent(Auction auction) { + this.auction = auction; + this.validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEventHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEventHandler.java new file mode 100644 index 0000000..1eed1d9 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/AuctionStartedEventHandler.java @@ -0,0 +1,6 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +public interface AuctionStartedEventHandler { + + boolean handleAuctionStartedEvent(AuctionStartedEvent auctionStartedEvent); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java new file mode 100644 index 0000000..5a53b94 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java @@ -0,0 +1,32 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskType; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +/** + * Event that notifies the auction house that an executor has been added to this TAPAS application. + */ +@Value +public class ExecutorAddedEvent extends SelfValidating { + @NotNull + private final ExecutorIdentifier executorId; + + @NotNull + private final AuctionedTaskType taskType; + + /** + * Constructs an executor added event. + * + * @param executorId the identifier of the executor that was added to this TAPAS application + */ + public ExecutorAddedEvent(ExecutorIdentifier executorId, AuctionedTaskType taskType) { + this.executorId = executorId; + this.taskType = taskType; + + this.validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEventHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEventHandler.java new file mode 100644 index 0000000..ca82a1c --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEventHandler.java @@ -0,0 +1,6 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +public interface ExecutorAddedEventHandler { + + boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java new file mode 100644 index 0000000..4d5c910 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java @@ -0,0 +1,26 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +/** + * Event that notifies the auction house that an executor has been removed from this TAPAS application. + */ +@Value +public class ExecutorRemovedEvent extends SelfValidating { + @NotNull + private final ExecutorIdentifier executorId; + + /** + * Constructs an executor removed event. + * + * @param executorId the identifier of the executor that was removed from this TAPAS application + */ + public ExecutorRemovedEvent(ExecutorIdentifier executorId) { + this.executorId = executorId; + this.validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEventHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEventHandler.java new file mode 100644 index 0000000..6d92422 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEventHandler.java @@ -0,0 +1,6 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +public interface ExecutorRemovedEventHandler { + + boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java new file mode 100644 index 0000000..626fa49 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java @@ -0,0 +1,37 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +/** + * Command for launching an auction in this auction house. + */ +@Value +public class LaunchAuctionCommand extends SelfValidating { + @NotNull + private final Auction.AuctionedTaskUri taskUri; + + @NotNull + private final Auction.AuctionedTaskType taskType; + + private final Auction.AuctionDeadline deadline; + + /** + * Constructs the launch action command. + * + * @param taskUri the URI of the auctioned task + * @param taskType the type of the auctioned task + * @param deadline the deadline by which the auction should receive bids (can be null if none) + */ + public LaunchAuctionCommand(Auction.AuctionedTaskUri taskUri, Auction.AuctionedTaskType taskType, + Auction.AuctionDeadline deadline) { + this.taskUri = taskUri; + this.taskType = taskType; + this.deadline = deadline; + + this.validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionUseCase.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionUseCase.java new file mode 100644 index 0000000..261240b --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionUseCase.java @@ -0,0 +1,8 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Auction; + +public interface LaunchAuctionUseCase { + + Auction launchAuction(LaunchAuctionCommand command); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsQuery.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsQuery.java new file mode 100644 index 0000000..a77f267 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsQuery.java @@ -0,0 +1,7 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +/** + * Query used to retrieve open auctions. Although this query is empty, we model it to convey the + * domain semantics and to reduce coupling. + */ +public class RetrieveOpenAuctionsQuery { } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsUseCase.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsUseCase.java new file mode 100644 index 0000000..4f94df2 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/RetrieveOpenAuctionsUseCase.java @@ -0,0 +1,10 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Auction; + +import java.util.Collection; + +public interface RetrieveOpenAuctionsUseCase { + + Collection retrieveAuctions(RetrieveOpenAuctionsQuery query); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionStartedEventPort.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionStartedEventPort.java new file mode 100644 index 0000000..9a432c9 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionStartedEventPort.java @@ -0,0 +1,11 @@ +package ch.unisg.tapas.auctionhouse.application.port.out; + +import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent; + +/** + * Port for sending out auction started events + */ +public interface AuctionStartedEventPort { + + void publishAuctionStartedEvent(AuctionStartedEvent event); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionWonEventPort.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionWonEventPort.java new file mode 100644 index 0000000..7ed440f --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/AuctionWonEventPort.java @@ -0,0 +1,11 @@ +package ch.unisg.tapas.auctionhouse.application.port.out; + +import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent; + +/** + * Port for sending out auction won events + */ +public interface AuctionWonEventPort { + + void publishAuctionWonEvent(AuctionWonEvent event); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommand.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommand.java new file mode 100644 index 0000000..e207891 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommand.java @@ -0,0 +1,25 @@ +package ch.unisg.tapas.auctionhouse.application.port.out; + +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Bid; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +/** + * Command to place a bid for a given auction. + */ +@Value +public class PlaceBidForAuctionCommand extends SelfValidating { + @NotNull + private final Auction auction; + @NotNull + private final Bid bid; + + public PlaceBidForAuctionCommand(Auction auction, Bid bid) { + this.auction = auction; + this.bid = bid; + this.validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommandPort.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommandPort.java new file mode 100644 index 0000000..3bf5a16 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/out/PlaceBidForAuctionCommandPort.java @@ -0,0 +1,6 @@ +package ch.unisg.tapas.auctionhouse.application.port.out; + +public interface PlaceBidForAuctionCommandPort { + + void placeBid(PlaceBidForAuctionCommand command); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/RetrieveOpenAuctionsService.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/RetrieveOpenAuctionsService.java new file mode 100644 index 0000000..bdba393 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/RetrieveOpenAuctionsService.java @@ -0,0 +1,22 @@ +package ch.unisg.tapas.auctionhouse.application.service; + +import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsQuery; +import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsUseCase; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +/** + * Service that implements {@link RetrieveOpenAuctionsUseCase} to retrieve all auctions in this auction + * house that are open for bids. + */ +@Component +public class RetrieveOpenAuctionsService implements RetrieveOpenAuctionsUseCase { + + @Override + public Collection retrieveAuctions(RetrieveOpenAuctionsQuery query) { + return AuctionRegistry.getInstance().getOpenAuctions(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java new file mode 100644 index 0000000..42c6e37 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java @@ -0,0 +1,113 @@ +package ch.unisg.tapas.auctionhouse.application.service; + +import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionCommand; +import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase; +import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort; +import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort; +import ch.unisg.tapas.auctionhouse.domain.*; +import ch.unisg.tapas.common.ConfigProperties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Service that implements the {@link LaunchAuctionUseCase} to start an auction. If a deadline is + * specified for the auction, the service automatically closes the auction at the deadline. If a + * deadline is not specified, the service closes the auction after 10s by default. + */ +@Component +public class StartAuctionService implements LaunchAuctionUseCase { + private static final Logger LOGGER = LogManager.getLogger(StartAuctionService.class); + + private final static int DEFAULT_AUCTION_DEADLINE_MILLIS = 10000; + + // Event port used to publish an auction started event + private final AuctionStartedEventPort auctionStartedEventPort; + // Event port used to publish an auction won event + private final AuctionWonEventPort auctionWonEventPort; + + private final ScheduledExecutorService service; + private final AuctionRegistry auctions; + + @Autowired + private ConfigProperties config; + + public StartAuctionService(AuctionStartedEventPort auctionStartedEventPort, + AuctionWonEventPort auctionWonEventPort) { + this.auctionStartedEventPort = auctionStartedEventPort; + this.auctionWonEventPort = auctionWonEventPort; + this.auctions = AuctionRegistry.getInstance(); + this.service = Executors.newScheduledThreadPool(1); + } + + /** + * Launches an auction. + * + * @param command the domain command used to launch the auction (see {@link LaunchAuctionCommand}) + * @return the launched auction + */ + @Override + public Auction launchAuction(LaunchAuctionCommand command) { + Auction.AuctionDeadline deadline = (command.getDeadline() == null) ? + new Auction.AuctionDeadline(DEFAULT_AUCTION_DEADLINE_MILLIS) : command.getDeadline(); + + // Create a new auction and add it to the auction registry + Auction auction = new Auction(new Auction.AuctionHouseUri(config.getAuctionHouseUri()), + command.getTaskUri(), command.getTaskType(), deadline); + auctions.addAuction(auction); + + // Schedule the closing of the auction at the deadline + service.schedule(new CloseAuctionTask(auction.getAuctionId()), deadline.getValue(), + TimeUnit.MILLISECONDS); + + // Publish an auction started event + AuctionStartedEvent auctionStartedEvent = new AuctionStartedEvent(auction); + auctionStartedEventPort.publishAuctionStartedEvent(auctionStartedEvent); + + return auction; + } + + /** + * This task closes the auction at the deadline and selects a winner if any bids were placed. It + * also sends out associated events and commands. + */ + private class CloseAuctionTask implements Runnable { + Auction.AuctionId auctionId; + + public CloseAuctionTask(Auction.AuctionId auctionId) { + this.auctionId = auctionId; + } + + @Override + public void run() { + Optional auctionOpt = auctions.getAuctionById(auctionId); + + if (auctionOpt.isPresent()) { + Auction auction = auctionOpt.get(); + Optional bid = auction.selectBid(); + + // Close the auction + auction.close(); + + if (bid.isPresent()) { + // Notify the bidder + Bid.BidderName bidderName = bid.get().getBidderName(); + LOGGER.info("Auction #" + auction.getAuctionId().getValue() + " for task " + + auction.getTaskUri().getValue() + " won by " + bidderName.getValue()); + + // Send an auction won event for the winning bid + auctionWonEventPort.publishAuctionWonEvent(new AuctionWonEvent(bid.get())); + } else { + LOGGER.info("Auction #" + auction.getAuctionId().getValue() + " ended with no bids for task " + + auction.getTaskUri().getValue()); + } + } + } + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java new file mode 100644 index 0000000..3e51ef7 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java @@ -0,0 +1,171 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Getter; +import lombok.Value; + +import java.net.URI; +import java.util.*; + +/** + * Domain entity that models an auction. + */ +public class Auction { + // Auctions have two possible states: + // - open: waiting for bids + // - closed: the auction deadline has expired, there may or may not be a winning bid + public enum Status { + OPEN, CLOSED + } + + // One way to generate auction identifiers is incremental starting from 1. This makes identifiers + // predictable, which can help with debugging when multiple parties are interacting, but it also + // means that auction identifiers are not universally unique unless they are part of a URI. + // An alternative would be to use UUIDs (see constructor). + private static long AUCTION_COUNTER = 1; + + @Getter + private AuctionId auctionId; + + @Getter + private AuctionStatus auctionStatus; + + // URI that identifies the auction house that started this auction. Given a uniform, standard + // HTTP API for auction houses, this URI can then be used as a base URI for interacting with + // the identified auction house. + @Getter + private final AuctionHouseUri auctionHouseUri; + + // URI that identifies the task for which the auction was launched. URIs are uniform identifiers + // and can be referenced independent of context: because we have defined a uniform HTTP API for + // TAPAS-Tasks, we can dereference this URI to retrieve a complete representation of the + // auctioned task. + @Getter + private final AuctionedTaskUri taskUri; + + // The type of the task being auctioned. We could also retrieve the task type by dereferencing + // the task's URI, but given that the bidding is defined primarily based on task types, knowing + // the task type avoids an additional HTTP request. + @Getter + private final AuctionedTaskType taskType; + + // The deadline by which bids can be placed. Once the deadline expires, the auction is closed. + @Getter + private final AuctionDeadline deadline; + + // Available bids. + @Getter + private final List bids; + + /** + * Constructs an auction. + * + * @param auctionHouseUri the URI of the auction hause that started the auction + * @param taskUri the URI of the task being auctioned + * @param taskType the type of the task being auctioned + * @param deadline the deadline by which the auction is open for bids + */ + public Auction(AuctionHouseUri auctionHouseUri, AuctionedTaskUri taskUri, + AuctionedTaskType taskType, AuctionDeadline deadline) { + // Generates an incremental identifier + this.auctionId = new AuctionId("" + AUCTION_COUNTER ++); + // As an alternative, we could also generate an UUID + // this.auctionId = new AuctionId(UUID.randomUUID().toString()); + + this.auctionStatus = new AuctionStatus(Status.OPEN); + + this.auctionHouseUri = auctionHouseUri; + this.taskUri = taskUri; + this.taskType = taskType; + + this.deadline = deadline; + this.bids = new ArrayList<>(); + } + + /** + * Constructs an auction. + * + * @param auctionId the identifier of the auction + * @param auctionHouseUri the URI of the auction hause that started the auction + * @param taskUri the URI of the task being auctioned + * @param taskType the type of the task being auctioned + * @param deadline the deadline by which the auction is open for bids + */ + public Auction(AuctionId auctionId, AuctionHouseUri auctionHouseUri, AuctionedTaskUri taskUri, + AuctionedTaskType taskType, AuctionDeadline deadline) { + this(auctionHouseUri, taskUri, taskType, deadline); + this.auctionId = auctionId; + } + + /** + * Places a bid for this auction. + * + * @param bid the bid + */ + public void addBid(Bid bid) { + bids.add(bid); + } + + /** + * Selects a bid randomly from the bids available for this auction. + * + * @return a winning bid or Optional.empty if no bid was made in this auction. + */ + public Optional selectBid() { + if (bids.isEmpty()) { + return Optional.empty(); + } + + int index = new Random().nextInt(bids.size()); + return Optional.of(bids.get(index)); + } + + /** + * Checks if the auction is open for bids. + * + * @return true if open for bids, false if the auction is closed + */ + public boolean isOpen() { + return auctionStatus.getValue() == Status.OPEN; + } + + /** + * Closes the auction. Called by the StartAuctionService after the auction deadline has expired. + */ + public void close() { + auctionStatus = new AuctionStatus(Status.CLOSED); + } + + /* + * Definitions of Value Objects + */ + + @Value + public static class AuctionId { + String value; + } + + @Value + public static class AuctionStatus { + Status value; + } + + @Value + public static class AuctionHouseUri { + URI value; + } + + @Value + public static class AuctionedTaskUri { + URI value; + } + + @Value + public static class AuctionedTaskType { + String value; + } + + @Value + public static class AuctionDeadline { + int value; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionRegistry.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionRegistry.java new file mode 100644 index 0000000..858589d --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionRegistry.java @@ -0,0 +1,105 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import java.util.Collection; +import java.util.Hashtable; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Registry that keeps an in-memory history of auctions (both open for bids and closed). This class + * is a singleton. See also {@link Auction}. + */ +public class AuctionRegistry { + private static AuctionRegistry registry; + + private final Map auctions; + + private AuctionRegistry() { + this.auctions = new Hashtable<>(); + } + + /** + * Retrieves a reference to the auction registry. + * + * @return the auction registry + */ + public static synchronized AuctionRegistry getInstance() { + if (registry == null) { + registry = new AuctionRegistry(); + } + + return registry; + } + + /** + * Adds a new auction to the registry + * @param auction the new auction + */ + public void addAuction(Auction auction) { + auctions.put(auction.getAuctionId(), auction); + } + + /** + * Places a bid. See also {@link Bid}. + * + * @param bid the bid to be placed. + * @return false if the bid is for an auction with an unknown identifier, true otherwise + */ + public boolean placeBid(Bid bid) { + if (!containsAuctionWithId(bid.getAuctionId())) { + return false; + } + + Auction auction = getAuctionById(bid.getAuctionId()).get(); + auction.addBid(bid); + auctions.put(bid.getAuctionId(), auction); + + return true; + } + + /** + * Checks if the registry contains an auction with the given identifier. + * + * @param auctionId the auction's identifier + * @return true if the registry contains an auction with the given identifier, false otherwise + */ + public boolean containsAuctionWithId(Auction.AuctionId auctionId) { + return auctions.containsKey(auctionId); + } + + /** + * Retrieves the auction with the given identifier if it exists. + * + * @param auctionId the auction's identifier + * @return the auction or Optional.empty if the identifier is unknown + */ + public Optional getAuctionById(Auction.AuctionId auctionId) { + if (containsAuctionWithId(auctionId)) { + return Optional.of(auctions.get(auctionId)); + } + + return Optional.empty(); + } + + /** + * Retrieves all auctions in the registry. + * + * @return a collection with all auctions + */ + public Collection getAllAuctions() { + return auctions.values(); + } + + /** + * Retrieves only the auctions that are open for bids. + * + * @return a collection with all open auctions + */ + public Collection getOpenAuctions() { + return getAllAuctions() + .stream() + .filter(auction -> auction.isOpen()) + .collect(Collectors.toList()); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionStartedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionStartedEvent.java new file mode 100644 index 0000000..7cac1ec --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionStartedEvent.java @@ -0,0 +1,15 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Getter; + +/** + * A domain event that models an auction has started. + */ +public class AuctionStartedEvent { + @Getter + private Auction auction; + + public AuctionStartedEvent(Auction auction) { + this.auction = auction; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionWonEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionWonEvent.java new file mode 100644 index 0000000..484646c --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/AuctionWonEvent.java @@ -0,0 +1,16 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Getter; + +/** + * A domain event that models an auction was won. + */ +public class AuctionWonEvent { + // The winning bid + @Getter + private Bid winningBid; + + public AuctionWonEvent(Bid winningBid) { + this.winningBid = winningBid; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Bid.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Bid.java new file mode 100644 index 0000000..4f24f30 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Bid.java @@ -0,0 +1,66 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Getter; +import lombok.Value; + +import java.net.URI; + +/** + * Domain entity that models a bid. + */ +public class Bid { + // The identifier of the auction for which the bid is placed + @Getter + private final Auction.AuctionId auctionId; + + // The name of the bidder, i.e. the identifier of the TAPAS group + @Getter + private final BidderName bidderName; + + // URI that identifies the auction house of the bidder. Given a uniform, standard HTTP API for + // auction houses, this URI can then be used as a base URI for interacting with the auction house + // of the bidder. + @Getter + private final BidderAuctionHouseUri bidderAuctionHouseUri; + + // URI that identifies the TAPAS-Tasks task list of the bidder. Given a uniform, standard HTTP API + // for TAPAS-Tasks, this URI can then be used as a base URI for interacting with the list of tasks + // of the bidder, e.g. to delegate a task. + @Getter + private final BidderTaskListUri bidderTaskListUri; + + /** + * Constructs a bid. + * + * @param auctionId the identifier of the auction for which the bid is placed + * @param bidderName the name of the bidder, i.e. the identifier of the TAPAS group + * @param auctionHouseUri the URI of the bidder's auction house + * @param taskListUri the URI fo the bidder's list of tasks + */ + public Bid(Auction.AuctionId auctionId, BidderName bidderName, BidderAuctionHouseUri auctionHouseUri, + BidderTaskListUri taskListUri) { + this.auctionId = auctionId; + this.bidderName = bidderName; + this.bidderAuctionHouseUri = auctionHouseUri; + this.bidderTaskListUri = taskListUri; + } + + /* + * Definitions of Value Objects + */ + + @Value + public static class BidderName { + private String value; + } + + @Value + public static class BidderAuctionHouseUri { + private URI value; + } + + @Value + public static class BidderTaskListUri { + private URI value; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java new file mode 100644 index 0000000..9da3756 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java @@ -0,0 +1,86 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Value; + +import java.util.*; + +/** + * Registry that keeps a track of executors internal to the TAPAS application and the types of tasks + * they can achieve. One executor may correspond to multiple task types. This mapping is used when + * bidding for tasks: the auction house will only bid for tasks for which there is a known executor. + * This class is a singleton. + */ +public class ExecutorRegistry { + private static ExecutorRegistry registry; + + private final Map> executors; + + private ExecutorRegistry() { + this.executors = new Hashtable<>(); + } + + public static synchronized ExecutorRegistry getInstance() { + if (registry == null) { + registry = new ExecutorRegistry(); + } + + return registry; + } + + /** + * Adds an executor to the registry for a given task type. + * + * @param taskType the type of the task + * @param executorIdentifier the identifier of the executor (can be any string) + * @return true unless a runtime exception occurs + */ + public boolean addExecutor(Auction.AuctionedTaskType taskType, ExecutorIdentifier executorIdentifier) { + Set taskTypeExecs = executors.getOrDefault(taskType, + Collections.synchronizedSet(new HashSet<>())); + + taskTypeExecs.add(executorIdentifier); + executors.put(taskType, taskTypeExecs); + + return true; + } + + /** + * Removes an executor from the registry. The executor is disassociated from all known task types. + * + * @param executorIdentifier the identifier of the executor (can be any string) + * @return true unless a runtime exception occurs + */ + public boolean removeExecutor(ExecutorIdentifier executorIdentifier) { + Iterator iterator = executors.keySet().iterator(); + + while (iterator.hasNext()) { + Auction.AuctionedTaskType taskType = iterator.next(); + Set set = executors.get(taskType); + + set.remove(executorIdentifier); + + if (set.isEmpty()) { + iterator.remove(); + } + } + + return true; + } + + /** + * Checks if the registry contains an executor for a given task type. Used during an auction to + * decide if a bid should be placed. + * + * @param taskType the task type being auctioned + * @return + */ + public boolean containsTaskType(Auction.AuctionedTaskType taskType) { + return executors.containsKey(taskType); + } + + // Value Object for the executor identifier + @Value + public static class ExecutorIdentifier { + String value; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/common/AuctionHouseResourceDirectory.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/AuctionHouseResourceDirectory.java new file mode 100644 index 0000000..c4809ef --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/AuctionHouseResourceDirectory.java @@ -0,0 +1,57 @@ +package ch.unisg.tapas.common; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +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.ArrayList; +import java.util.List; + +/** + * Class that wraps up the resource directory used to discover auction houses in Week 6. + */ +public class AuctionHouseResourceDirectory { + private final URI rdEndpoint; + + /** + * Constructs a resource directory for auction house given a known URI. + * + * @param rdEndpoint the based endpoint of the resource directory + */ + public AuctionHouseResourceDirectory(URI rdEndpoint) { + this.rdEndpoint = rdEndpoint; + } + + /** + * Retrieves the endpoints of all auctions houses registered with this directory. + * @return + */ + public List retrieveAuctionHouseEndpoints() { + List auctionHouseEndpoints = new ArrayList<>(); + + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(rdEndpoint).GET().build(); + + HttpResponse response = HttpClient.newBuilder().build() + .send(request, HttpResponse.BodyHandlers.ofString()); + + // For simplicity, here we just hard code the current representation used by our + // resource directory for auction houses + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode payload = objectMapper.readTree(response.body()); + + for (JsonNode node : payload) { + auctionHouseEndpoints.add(node.get("endpoint").asText()); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return auctionHouseEndpoints; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java new file mode 100644 index 0000000..748afda --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java @@ -0,0 +1,64 @@ +package ch.unisg.tapas.common; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.net.URI; + +/** + * Used to access properties provided via application.properties + */ +@Component +public class ConfigProperties { + @Autowired + private Environment environment; + + /** + * Retrieves the URI of the WebSub hub. In this project, we use a single WebSub hub, but we could + * use multiple. + * + * @return the URI of the WebSub hub + */ + public URI getWebSubHub() { + return URI.create(environment.getProperty("websub.hub")); + } + + /** + * Retrieves the URI used to publish content via WebSub. In this project, we use a single + * WebSub hub, but we could use multiple. This URI is usually different from the WebSub hub URI. + * + * @return URI used to publish content via the WebSub hub + */ + public URI getWebSubPublishEndpoint() { + return URI.create(environment.getProperty("websub.hub.publish")); + } + + /** + * Retrieves the name of the group providing this auction house. + * + * @return the identifier of the group, e.g. tapas-group1 + */ + public String getGroupName() { + return environment.getProperty("group"); + } + + /** + * Retrieves the base URI of this auction house. + * + * @return the base URI of this auction house + */ + public URI getAuctionHouseUri() { + return URI.create(environment.getProperty("auction.house.uri")); + } + + /** + * Retrieves the URI of the TAPAS-Tasks task list of this TAPAS applicatoin. This is used, e.g., + * when placing a bid during the auction (see also {@link ch.unisg.tapas.auctionhouse.domain.Bid}). + * + * @return + */ + public URI getTaskListUri() { + return URI.create(environment.getProperty("tasks.list.uri")); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/common/SelfValidating.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/SelfValidating.java new file mode 100644 index 0000000..1b56db4 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/SelfValidating.java @@ -0,0 +1,25 @@ +package ch.unisg.tapas.common; + +import javax.validation.*; +import java.util.Set; + +public class SelfValidating { + + private Validator validator; + + public SelfValidating() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Evaluates all Bean Validations on the attributes of this + * instance. + */ + protected void validateSelf() { + Set> violations = validator.validate((T) this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties new file mode 100644 index 0000000..e9c609f --- /dev/null +++ b/tapas-auction-house/src/main/resources/application.properties @@ -0,0 +1,8 @@ +server.port=8086 + +websub.hub=https://websub.appspot.com/ +websub.hub.publish=https://websub.appspot.com/ + +group=tapas-group-tutors +auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ +tasks.list.uri=https://tapas-tasks.86-119-34-23.nip.io/ diff --git a/tapas-auction-house/src/test/java/ch/unisg/tapas/TapasAuctionHouseApplicationTests.java b/tapas-auction-house/src/test/java/ch/unisg/tapas/TapasAuctionHouseApplicationTests.java new file mode 100644 index 0000000..ce414c3 --- /dev/null +++ b/tapas-auction-house/src/test/java/ch/unisg/tapas/TapasAuctionHouseApplicationTests.java @@ -0,0 +1,13 @@ +package ch.unisg.tapas; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TapasAuctionHouseApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/tapas-tasks/README.md b/tapas-tasks/README.md index f083776..90016c3 100644 --- a/tapas-tasks/README.md +++ b/tapas-tasks/README.md @@ -11,61 +11,159 @@ with default editor settings. EditorConfig is supported out-of-the-box by the In consistent code styles, we recommend to reuse this editor configuration file in all your services. ## HTTP API Overview -The code we provide includes a minimalistic HTTP API for (i) creating a new task and (ii) retrieving -the representation of a task. +The code we provide includes a minimalistic uniform HTTP API for (i) creating a new task, (ii) retrieving +a representation of the current state of a task, and (iii) patching the representation of a task, which +is mapped to a domain/integration event. + +The representations exchanged with the API use two media types: +* a JSON-based format for task with the media type `application/task+json`; this media type is defined + in the context of our project, but could be [registered with IANA](https://www.iana.org/assignments/media-types) + to promote interoperability (see + [TaskJsonRepresentation](src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java) + for more details) +* the [JSON Patch](http://jsonpatch.com/) format with the registered media type `application/json-patch+json`, which is also a + JSON-based format (see sample HTTP requests below). For further developing and working with your HTTP API, we recommend to use [Postman](https://www.postman.com/). ### Creating a new task A new task is created via an `HTTP POST` request to the `/tasks/` endpoint. The body of the request -must include a JSON payload with the content type `application/json` and two required fields: +must include a representation of the task to be created using the content type `application/task+json` +defined in the context of this project. A valid representation must include at least two required fields +(see [TaskJsonRepresentation](src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java) +for more details): * `taskName`: a string that represents the name of the task to be created * `taskType`: a string that represents the type of the task to be created A sample HTTP request with `curl`: ```shell -curl -i --location --request POST 'http://localhost:8081/tasks/' --header 'Content-Type: application/json' --data-raw '{ - "taskName" : "task1", - "taskType" : "type1" +curl -i --location --request POST 'http://localhost:8081/tasks/' \ +--header 'Content-Type: application/task+json' \ +--data-raw '{ + "taskName" : "task1", + "taskType" : "computation", + "originalTaskUri" : "http://example.org", + "inputData" : "1+1" }' HTTP/1.1 201 -Content-Type: application/json -Content-Length: 142 -Date: Sun, 03 Oct 2021 17:25:32 GMT +Location: http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354 +Content-Type: application/task+json +Content-Length: 170 +Date: Sun, 17 Oct 2021 21:03:34 GMT { - "taskType" : "type1", - "taskState" : "OPEN", - "taskListName" : "tapas-tasks-tutors", - "taskName" : "task1", - "taskId" : "53cb19d6-2d9b-486f-98c7-c96c93b037f0" + "taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354", + "taskName":"task1", + "taskType":"computation", + "taskStatus":"OPEN", + "originalTaskUri":"http://example.org", + "inputData":"1+1" } ``` -If the task is created successfuly, a `201 Created` status code is returned together with a JSON -representation of the created task. The representation includes, among others, a _universally unique -identifier (UUID)_ for the newly created task (`taskId`). +If the task is created successfuly, a `201 Created` status code is returned together with a +representation of the created task. The response also includes a `Location` header filed that points +to the URI of the newly created task. ### Retrieving a task -The representation of a task is retrieved via an `HTTP GET` request to the `/tasks/` endpoint. +The representation of a task is retrieved via an `HTTP GET` request to the URI of task. A sample HTTP request with `curl`: ```shell -curl -i --location --request GET 'http://localhost:8081/tasks/53cb19d6-2d9b-486f-98c7-c96c93b037f0' +curl -i --location --request GET 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354' HTTP/1.1 200 -Content-Type: application/json -Content-Length: 142 -Date: Sun, 03 Oct 2021 17:27:06 GMT +Content-Type: application/task+json +Content-Length: 170 +Date: Sun, 17 Oct 2021 21:07:04 GMT { - "taskType" : "type1", - "taskState" : "OPEN", - "taskListName" : "tapas-tasks-tutors", - "taskName" : "task1", - "taskId" : "53cb19d6-2d9b-486f-98c7-c96c93b037f0" + "taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354", + "taskName":"task1", + "taskType":"computation", + "taskStatus":"OPEN", + "originalTaskUri":"http://example.org", + "inputData":"1+1" } ``` + +### Patching a task + +REST emphasizes the generality of interfaces to promote uniform interaction. For instance, we can use +the `HTTP PATCH` method to implement fine-grained updates to the representational state of a task, which +may translate to various domain/integration events. However, to conform to the uniform interface +contraint in REST, any such updates have to rely on standard knowledge — and thus to hide away the +implementation details of our service. + +In addition to the `application/task+json` media type we defined for our uniform HTTP API, a standard +representation format we can use to specify fine-grained updates to the representation of tasks +is [JSON Patch](http://jsonpatch.com/). In what follow, we provide a few examples of `HTTP PATCH` requests. +For further details on the JSON Patch format, see also [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)). + +#### Changing the status of a task from OPEN to ASSIGNED + +Sample HTTP request that assigns the previously created task to group `tapas-group1`: + +```shell +curl -i --location --request PATCH 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354' \ +--header 'Content-Type: application/json-patch+json' \ +--data-raw '[ {"op" : "replace", "path": "/taskStatus", "value" : "ASSIGNED" }, + {"op" : "add", "path": "/serviceProvider", "value" : "tapas-group1" } ]' + +HTTP/1.1 200 +Content-Type: application/task+json +Content-Length: 207 +Date: Sun, 17 Oct 2021 21:20:58 GMT + +{ + "taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354", + "taskName":"task1", + "taskType":"computation", + "taskStatus":"ASSIGNED", + "originalTaskUri":"http://example.org", + "serviceProvider":"tapas-group1", + "inputData":"1+1" +} +``` + +In this example, the requested patch includes two JSON Patch operations: +* an operation to `replace` the `taskStatus` already in the task's representation with the value `ASSIGNED` +* an operation to `add` to the task's representation a `serviceProvider` with the value `tapas-group1` + +Internally, this request is mapped to a +[TaskAssignedEvent](src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEvent.java). +The HTTP response returns a `200 OK` status code together with the updated representation of the task. + +#### Changing the status of a task from to EXECUTED + +Sample HTTP request that changes the status of the task to `EXECUTED` and adds an output result: + +```shell +curl -i --location --request PATCH 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354' \ +--header 'Content-Type: application/json-patch+json' \ +--data-raw '[ {"op" : "replace", "path": "/taskStatus", "value" : "EXECUTED" }, + {"op" : "add", "path": "/outputData", "value" : "2" } ]' + +HTTP/1.1 200 +Content-Type: application/task+json +Content-Length: 224 +Date: Sun, 17 Oct 2021 21:32:25 GMT + +{ + "taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354", + "taskName":"task1", + "taskType":"computation", + "taskStatus":"EXECUTED", + "originalTaskUri":"http://example.org", + "serviceProvider":"tapas-group1", + "inputData":"1+1", + "outputData":"2" +} +``` + +Internally, this request is mapped to a +[TaskExecutedEvent](src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java). +The HTTP response returns a `200 OK` status code together with the updated representation of the task. diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index f3989f4..0118cf9 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -18,6 +18,12 @@ scs-asse-fs21-group1 https://sonarcloud.io + + + Eclipse Paho Repo + https://repo.eclipse.org/content/repositories/paho-releases/ + + org.springframework.boot @@ -51,11 +57,6 @@ 1.1.0.Final - - org.json - json - 20210307 - com.github.java-json-tools json-patch @@ -69,6 +70,11 @@ true + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.0 + diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java index 90d1716..2675391 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java @@ -3,15 +3,12 @@ package ch.unisg.tapastasks; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import java.util.Collections; - @SpringBootApplication public class TapasTasksApplication { public static void main(String[] args) { - - SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); - tapasTasksApp.run(args); + SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); + tapasTasksApp.run(args); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonPatchRepresentation.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonPatchRepresentation.java new file mode 100644 index 0000000..94c1f47 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonPatchRepresentation.java @@ -0,0 +1,102 @@ +package ch.unisg.tapastasks.tasks.adapter.in.formats; + +import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * This class is used to process JSON Patch operations for tasks: given a + * JSON Patch for updating the representational state of a task, + * this class provides methods for extracting various operations of interest for our domain (e.g., + * changing the status of a task). + */ +public class TaskJsonPatchRepresentation { + public static final String MEDIA_TYPE = "application/json-patch+json"; + + private final JsonNode patch; + + /** + * Constructs the JSON Patch representation. + * + * @param patch a JSON Patch as JsonNode + */ + public TaskJsonPatchRepresentation(JsonNode patch) { + this.patch = patch; + } + + /** + * Extracts the first task status replaced in this patch. + * + * @return the first task status changed in this patch or an empty {@link Optional} if none is + * found + */ + public Optional extractFirstTaskStatusChange() { + Optional status = extractFirst(node -> + isPatchReplaceOperation(node) && hasPath(node, "/taskStatus") + ); + + if (status.isPresent()) { + String taskStatus = status.get().get("value").asText(); + return Optional.of(Task.Status.valueOf(taskStatus)); + } + + return Optional.empty(); + } + + /** + * Extracts the first service provider added or replaced in this patch. + * + * @return the first service provider changed in this patch or an empty {@link Optional} if none + * is found + */ + public Optional extractFirstServiceProviderChange() { + Optional serviceProvider = extractFirst(node -> + (isPatchReplaceOperation(node) || isPatchAddOperation(node)) + && hasPath(node, "/serviceProvider") + ); + + return (serviceProvider.isEmpty()) ? Optional.empty() + : Optional.of(new Task.ServiceProvider(serviceProvider.get().get("value").asText())); + } + + /** + * Extracts the first output data addition in this patch. + * + * @return the output data added in this patch or an empty {@link Optional} if none is found + */ + public Optional extractFirstOutputDataAddition() { + Optional output = extractFirst(node -> + isPatchAddOperation(node) && hasPath(node, "/outputData") + ); + + return (output.isEmpty()) ? Optional.empty() + : Optional.of(new Task.OutputData(output.get().get("value").asText())); + } + + private Optional extractFirst(Predicate predicate) { + Stream stream = StreamSupport.stream(patch.spliterator(), false); + return stream.filter(predicate).findFirst(); + } + + private boolean isPatchAddOperation(JsonNode node) { + return isPatchOperationOfType(node, "add"); + } + + private boolean isPatchReplaceOperation(JsonNode node) { + return isPatchOperationOfType(node, "replace"); + } + + private boolean isPatchOperationOfType(JsonNode node, String operation) { + return node.isObject() && node.get("op") != null + && node.get("op").asText().equalsIgnoreCase(operation); + } + + private boolean hasPath(JsonNode node, String path) { + return node.isObject() && node.get("path") != null + && node.get("path").asText().equalsIgnoreCase(path); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java new file mode 100644 index 0000000..eb89415 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java @@ -0,0 +1,115 @@ +package ch.unisg.tapastasks.tasks.adapter.in.formats; + +import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +/** + * This class is used to expose and consume representations of tasks through the HTTP interface. The + * representations conform to the custom JSON-based media type "application/task+json". The media type + * is just an identifier and can be registered with + * IANA to promote interoperability. + */ +final public class TaskJsonRepresentation { + // The media type used for this task representation format + public static final String MEDIA_TYPE = "application/task+json"; + + // A task identifier specific to our implementation (e.g., a UUID). This identifier is then used + // to generate the task's URI. URIs are standard uniform identifiers and use a universal syntax + // that can be referenced (and dereferenced) independent of context. In our uniform HTTP API, + // we identify tasks via URIs and not implementation-specific identifiers. + @Getter @Setter + private String taskId; + + // A string that represents the task's name + @Getter + private final String taskName; + + // A string that identifies the task's type. This string could also be a URI (e.g., defined in some + // Web ontology, as we shall see later in the course), but it's not constrained to be a URI. + // The task's type can be used to assign executors to tasks, to decide what tasks to bid for, etc. + @Getter + private final String taskType; + + // The task's status: OPEN, ASSIGNED, RUNNING, or EXECUTED (see Task.Status) + @Getter @Setter + private String taskStatus; + + // If this task is a delegated task (i.e., a shadow of another task), this URI points to the + // original task. Because URIs are standard and uniform, we can just dereference this URI to + // retrieve a representation of the original task. + @Getter @Setter + private String originalTaskUri; + + // The service provider who executes this task. The service provider is a any string that identifies + // a TAPAS group (e.g., tapas-group1). This identifier could also be a URI (if we have a good reason + // for it), but it's not constrained to be a URI. + @Getter @Setter + private String serviceProvider; + + // A string that provides domain-specific input data for this task. In the context of this project, + // we can parse and interpret the input data based on the task's type. + @Getter @Setter + private String inputData; + + // A string that provides domain-specific output data for this task. In the context of this project, + // we can parse and interpret the output data based on the task's type. + @Getter @Setter + private String outputData; + + /** + * Instantiate a task representation with a task name and type. + * + * @param taskName string that represents the task's name + * @param taskType string that represents the task's type + */ + public TaskJsonRepresentation(String taskName, String taskType) { + this.taskName = taskName; + this.taskType = taskType; + + this.taskStatus = null; + this.originalTaskUri = null; + this.serviceProvider = null; + this.inputData = null; + this.outputData = null; + } + + /** + * Instantiate a task representation from a domain concept. + * + * @param task the task + */ + public TaskJsonRepresentation(Task task) { + this(task.getTaskName().getValue(), task.getTaskType().getValue()); + + this.taskId = task.getTaskId().getValue(); + this.taskStatus = task.getTaskStatus().getValue().name(); + + this.originalTaskUri = (task.getOriginalTaskUri() == null) ? + null : task.getOriginalTaskUri().getValue(); + + this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue(); + this.inputData = (task.getInputData() == null) ? null : task.getInputData().getValue(); + this.outputData = (task.getOutputData() == null) ? null : task.getOutputData().getValue(); + } + + /** + * Convenience method used to serialize a task provided as a domain concept in the format exposed + * through the uniform HTTP API. + * + * @param task the task as defined in the domain + * @return a string serialization using the JSON-based representation format defined for tasks + * @throws JsonProcessingException if a runtime exception occurs during object serialization + */ + public static String serialize(Task task) throws JsonProcessingException { + TaskJsonRepresentation representation = new TaskJsonRepresentation(task); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return mapper.writeValueAsString(representation); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/UnknownEventException.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/UnknownEventException.java new file mode 100644 index 0000000..fbeb7b7 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/UnknownEventException.java @@ -0,0 +1,3 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging; + +public class UnknownEventException extends RuntimeException { } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskAssignedEventListenerHttpAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskAssignedEventListenerHttpAdapter.java new file mode 100644 index 0000000..4c26b80 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskAssignedEventListenerHttpAdapter.java @@ -0,0 +1,39 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging.http; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation; +import ch.unisg.tapastasks.tasks.application.handler.TaskAssignedHandler; +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Optional; + +/** + * Listener for task assigned events. A task assigned event corresponds to a JSON Patch that attempts + * to change the task's status to ASSIGNED and may also add/replace a service provider (i.e., to what + * group the task was assigned). This implementation does not impose that a task assigned event + * includes the service provider (i.e., can be null). + * + * See also {@link TaskAssignedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}. + */ +public class TaskAssignedEventListenerHttpAdapter extends TaskEventListener { + + /** + * Handles the task assigned event. + * + * @param taskId the identifier of the task for which an event was received + * @param payload the JSON Patch payload of the HTTP PATCH request received for this task + * @return + */ + public Task handleTaskEvent(String taskId, JsonNode payload) { + TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload); + Optional serviceProvider = representation.extractFirstServiceProviderChange(); + + TaskAssignedEvent taskAssignedEvent = new TaskAssignedEvent(new TaskId(taskId), serviceProvider); + TaskAssignedEventHandler taskAssignedEventHandler = new TaskAssignedHandler(); + + return taskAssignedEventHandler.handleTaskAssigned(taskAssignedEvent); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventHttpDispatcher.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventHttpDispatcher.java new file mode 100644 index 0000000..940d6fa --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventHttpDispatcher.java @@ -0,0 +1,103 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging.http; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; +import ch.unisg.tapastasks.tasks.adapter.in.messaging.UnknownEventException; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException; +import com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jsonpatch.JsonPatch; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.io.IOException; +import java.util.Optional; + + +/** + * This REST Controller handles HTTP PATCH requests for updating the representational state of Task + * resources. Each request to update the representational state of a Task resource can correspond to + * at most one domain/integration event. Request payloads use the + * JSON PATCH format and media type. + * + * A JSON Patch can contain multiple operations (e.g., add, remove, replace) for updating various + * parts of a task's representations. One or more JSON Patch operations can represent a domain/integration + * event. Therefore, the events can only be determined by inspecting the requested patch (e.g., a request + * to change a task's status from RUNNING to EXECUTED). This class is responsible to inspect requested + * patches, identify events, and to route them to appropriate listeners. + * + * For more details on JSON Patch, see: http://jsonpatch.com/ + * For some sample HTTP requests, see the README. + */ +@RestController +public class TaskEventHttpDispatcher { + // The standard media type for JSON Patch registered with IANA + // See: https://www.iana.org/assignments/media-types/application/json-patch+json + private final static String JSON_PATCH_MEDIA_TYPE = "application/json-patch+json"; + + /** + * Handles HTTP PATCH requests with a JSON Patch payload. Routes the requests based on the + * the operations requested in the patch. In this implementation, one HTTP Patch request is + * mapped to at most one domain event. + * + * @param taskId the local (i.e., implementation-specific) identifier of the task to the patched; + * this identifier is extracted from the task's URI + * @param payload the reuqested patch for this task + * @return 200 OK and a representation of the task after processing the event; 404 Not Found if + * the request URI does not match any task; 400 Bad Request if the request is invalid + */ + @PatchMapping(path = "/tasks/{taskId}", consumes = {JSON_PATCH_MEDIA_TYPE}) + public ResponseEntity dispatchTaskEvents(@PathVariable("taskId") String taskId, + @RequestBody JsonNode payload) { + try { + // Throw an exception if the JSON Patch format is invalid. This call is only used to + // validate the JSON PATCH syntax. + JsonPatch.fromJson(payload); + + // Check for known events and route the events to appropriate listeners + TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload); + Optional status = representation.extractFirstTaskStatusChange(); + + TaskEventListener listener = null; + + // Route events related to task status changes + if (status.isPresent()) { + switch (status.get()) { + case ASSIGNED: + listener = new TaskAssignedEventListenerHttpAdapter(); + break; + case RUNNING: + listener = new TaskStartedEventListenerHttpAdapter(); + break; + case EXECUTED: + listener = new TaskExecutedEventListenerHttpAdapter(); + break; + } + } + + if (listener == null) { + // The HTTP PATCH request is valid, but the patch does not match any known event + throw new UnknownEventException(); + } + + Task task = listener.handleTaskEvent(taskId, payload); + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); + + return new ResponseEntity<>(TaskJsonRepresentation.serialize(task), responseHeaders, + HttpStatus.OK); + } catch (TaskNotFoundException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } catch (IOException | RuntimeException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventListener.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventListener.java new file mode 100644 index 0000000..8912968 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskEventListener.java @@ -0,0 +1,24 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging.http; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Abstract class that handles events specific to a Task. Events are received via an HTTP PATCH + * request for a given task and dispatched to Task event listeners (see {@link TaskEventHttpDispatcher}). + * Each listener must implement the abstract method {@link #handleTaskEvent(String, JsonNode)}, which + * may require additional event-specific validations. + */ +public abstract class TaskEventListener { + + /** + * This abstract method handles a task event and returns the task after the event was handled. + * + * @param taskId the identifier of the task for which an event was received + * @param payload the JSON Patch payload of the HTTP PATCH request received for this task + * @return the task for which the HTTP PATCH request is handled + * @throws TaskNotFoundException + */ + public abstract Task handleTaskEvent(String taskId, JsonNode payload) throws TaskNotFoundException; +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskExecutedEventListenerHttpAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskExecutedEventListenerHttpAdapter.java new file mode 100644 index 0000000..f1db541 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskExecutedEventListenerHttpAdapter.java @@ -0,0 +1,34 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging.http; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation; +import ch.unisg.tapastasks.tasks.application.handler.TaskExecutedHandler; +import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Optional; + +/** + * Listener for task executed events. A task executed event corresponds to a JSON Patch that attempts + * to change the task's status to EXECUTED, may add/replace a service provider, and may also add an + * output result. This implementation does not impose that a task executed event includes either the + * service provider or an output result (i.e., both can be null). + * + * See also {@link TaskExecutedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}. + */ +public class TaskExecutedEventListenerHttpAdapter extends TaskEventListener { + + public Task handleTaskEvent(String taskId, JsonNode payload) { + TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload); + + Optional serviceProvider = representation.extractFirstServiceProviderChange(); + Optional outputData = representation.extractFirstOutputDataAddition(); + + TaskExecutedEvent taskExecutedEvent = new TaskExecutedEvent(new Task.TaskId(taskId), + serviceProvider, outputData); + TaskExecutedEventHandler taskExecutedEventHandler = new TaskExecutedHandler(); + + return taskExecutedEventHandler.handleTaskExecuted(taskExecutedEvent); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskStartedEventListenerHttpAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskStartedEventListenerHttpAdapter.java new file mode 100644 index 0000000..aa2f6b4 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/messaging/http/TaskStartedEventListenerHttpAdapter.java @@ -0,0 +1,32 @@ +package ch.unisg.tapastasks.tasks.adapter.in.messaging.http; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation; +import ch.unisg.tapastasks.tasks.application.handler.TaskStartedHandler; +import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Optional; + +/** + * Listener for task started events. A task started event corresponds to a JSON Patch that attempts + * to change the task's status to RUNNING and may also add/replace a service provider. This + * implementation does not impose that a task started event includes the service provider (i.e., + * can be null). + * + * See also {@link TaskStartedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}. + */ +public class TaskStartedEventListenerHttpAdapter extends TaskEventListener { + + public Task handleTaskEvent(String taskId, JsonNode payload) { + TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload); + Optional serviceProvider = representation.extractFirstServiceProviderChange(); + + TaskStartedEvent taskStartedEvent = new TaskStartedEvent(new TaskId(taskId), serviceProvider); + TaskStartedEventHandler taskStartedEventHandler = new TaskStartedHandler(); + + return taskStartedEventHandler.handleTaskStarted(taskStartedEvent); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java index 53bebc1..234dcde 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java @@ -1,8 +1,12 @@ package ch.unisg.tapastasks.tasks.adapter.in.web; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand; import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase; import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -12,29 +16,67 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import javax.validation.ConstraintViolationException; +import java.util.Optional; +/** + * Controller that handles HTTP requests for creating new tasks. This controller implements the + * {@link AddNewTaskToTaskListUseCase} use case using the {@link AddNewTaskToTaskListCommand}. + * + * A new task is created via an HTTP POST request to the /tasks/ endpoint. The body of the request + * contains a JSON-based representation with the "application/task+json" media type defined for this + * project. This custom media type allows to capture the semantics of our JSON representations for + * tasks. + * + * If the request is successful, the controller returns an HTTP 201 Created status code and a + * representation of the created task with Content-Type "application/task+json". The HTTP response + * also include a Location header field that points to the URI of the created task. + */ @RestController public class AddNewTaskToTaskListWebController { private final AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase; + // Used to retrieve properties from application.properties + @Autowired + private Environment environment; + public AddNewTaskToTaskListWebController(AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase) { this.addNewTaskToTaskListUseCase = addNewTaskToTaskListUseCase; } - @PostMapping(path = "/tasks/", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) - public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { + @PostMapping(path = "/tasks/", consumes = {TaskJsonRepresentation.MEDIA_TYPE}) + public ResponseEntity addNewTaskTaskToTaskList(@RequestBody TaskJsonRepresentation payload) { try { - AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand( - task.getTaskName(), task.getTaskType() - ); + Task.TaskName taskName = new Task.TaskName(payload.getTaskName()); + Task.TaskType taskType = new Task.TaskType(payload.getTaskType()); - Task newTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command); + // If the created task is a delegated task, the representation contains a URI reference + // to the original task + Optional originalTaskUriOptional = + (payload.getOriginalTaskUri() == null) ? Optional.empty() + : Optional.of(new Task.OriginalTaskUri(payload.getOriginalTaskUri())); + + AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand(taskName, taskType, + originalTaskUriOptional); + + Task createdTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command); + + // When creating a task, the task's representation may include optional input data + if (payload.getInputData() != null) { + createdTask.setInputData(new Task.InputData(payload.getInputData())); + } // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); + // Construct and advertise the URI of the newly created task; we retrieve the base URI + // from the application.properties file + responseHeaders.add(HttpHeaders.LOCATION, environment.getProperty("baseuri") + + "tasks/" + createdTask.getTaskId().getValue()); - return new ResponseEntity<>(TaskMediaType.serialize(newTask), responseHeaders, HttpStatus.CREATED); + return new ResponseEntity<>(TaskJsonRepresentation.serialize(createdTask), responseHeaders, + HttpStatus.CREATED); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } catch (ConstraintViolationException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/RetrieveTaskFromTaskListWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/RetrieveTaskFromTaskListWebController.java index 0eb6bea..d60e4d1 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/RetrieveTaskFromTaskListWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/RetrieveTaskFromTaskListWebController.java @@ -1,8 +1,10 @@ package ch.unisg.tapastasks.tasks.adapter.in.web; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListQuery; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase; import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -11,6 +13,11 @@ import org.springframework.web.server.ResponseStatusException; import java.util.Optional; +/** + * Controller that handles HTTP GET requests for retrieving tasks. This controller implements the + * {@link RetrieveTaskFromTaskListUseCase} use case using the {@link RetrieveTaskFromTaskListQuery} + * query. + */ @RestController public class RetrieveTaskFromTaskListWebController { private final RetrieveTaskFromTaskListUseCase retrieveTaskFromTaskListUseCase; @@ -19,10 +26,17 @@ public class RetrieveTaskFromTaskListWebController { this.retrieveTaskFromTaskListUseCase = retrieveTaskFromTaskListUseCase; } + /** + * Retrieves a representation of task. Returns HTTP 200 OK if the request is successful with a + * representation of the task using the Content-Type "applicatoin/task+json". + * + * @param taskId the local identifier of the requested task (extracted from the task's URI) + * @return a representation of the task if the task exists + */ @GetMapping(path = "/tasks/{taskId}") public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskId") String taskId) { - RetrieveTaskFromTaskListQuery command = new RetrieveTaskFromTaskListQuery(new Task.TaskId(taskId)); - Optional updatedTaskOpt = retrieveTaskFromTaskListUseCase.retrieveTaskFromTaskList(command); + RetrieveTaskFromTaskListQuery query = new RetrieveTaskFromTaskListQuery(new Task.TaskId(taskId)); + Optional updatedTaskOpt = retrieveTaskFromTaskListUseCase.retrieveTaskFromTaskList(query); // Check if the task with the given identifier exists if (updatedTaskOpt.isEmpty()) { @@ -30,11 +44,16 @@ public class RetrieveTaskFromTaskListWebController { throw new ResponseStatusException(HttpStatus.NOT_FOUND); } - // Add the content type as a response header - HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + try { + String taskRepresentation = TaskJsonRepresentation.serialize(updatedTaskOpt.get()); - return new ResponseEntity<>(TaskMediaType.serialize(updatedTaskOpt.get()), responseHeaders, - HttpStatus.OK); + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); + + return new ResponseEntity<>(taskRepresentation, responseHeaders, HttpStatus.OK); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskAssignedHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskAssignedHandler.java new file mode 100644 index 0000000..7deb844 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskAssignedHandler.java @@ -0,0 +1,19 @@ +package ch.unisg.tapastasks.tasks.application.handler; + +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException; +import org.springframework.stereotype.Component; + +@Component +public class TaskAssignedHandler implements TaskAssignedEventHandler { + + @Override + public Task handleTaskAssigned(TaskAssignedEvent taskAssignedEvent) throws TaskNotFoundException { + TaskList taskList = TaskList.getTapasTaskList(); + return taskList.changeTaskStatusToAssigned(taskAssignedEvent.getTaskId(), + taskAssignedEvent.getServiceProvider()); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskExecutedHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskExecutedHandler.java new file mode 100644 index 0000000..ec21e8c --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskExecutedHandler.java @@ -0,0 +1,19 @@ +package ch.unisg.tapastasks.tasks.application.handler; + +import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException; +import org.springframework.stereotype.Component; + +@Component +public class TaskExecutedHandler implements TaskExecutedEventHandler { + + @Override + public Task handleTaskExecuted(TaskExecutedEvent taskExecutedEvent) throws TaskNotFoundException { + TaskList taskList = TaskList.getTapasTaskList(); + return taskList.changeTaskStatusToExecuted(taskExecutedEvent.getTaskId(), + taskExecutedEvent.getServiceProvider(), taskExecutedEvent.getOutputData()); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskStartedHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskStartedHandler.java new file mode 100644 index 0000000..758be0b --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/handler/TaskStartedHandler.java @@ -0,0 +1,19 @@ +package ch.unisg.tapastasks.tasks.application.handler; + +import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEvent; +import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEventHandler; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException; +import org.springframework.stereotype.Component; + +@Component +public class TaskStartedHandler implements TaskStartedEventHandler { + + @Override + public Task handleTaskStarted(TaskStartedEvent taskStartedEvent) throws TaskNotFoundException { + TaskList taskList = TaskList.getTapasTaskList(); + return taskList.changeTaskStatusToRunning(taskStartedEvent.getTaskId(), + taskStartedEvent.getServiceProvider()); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java index a0e0fec..fbb66ed 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java @@ -1,23 +1,30 @@ package ch.unisg.tapastasks.tasks.application.port.in; import ch.unisg.tapastasks.common.SelfValidating; -import ch.unisg.tapastasks.tasks.domain.Task.TaskType; -import ch.unisg.tapastasks.tasks.domain.Task.TaskName; +import ch.unisg.tapastasks.tasks.domain.Task; +import lombok.Getter; import lombok.Value; import javax.validation.constraints.NotNull; +import java.util.Optional; @Value public class AddNewTaskToTaskListCommand extends SelfValidating { @NotNull - private final TaskName taskName; + private final Task.TaskName taskName; @NotNull - private final TaskType taskType; + private final Task.TaskType taskType; - public AddNewTaskToTaskListCommand(TaskName taskName, TaskType taskType) { + @Getter + private final Optional originalTaskUri; + + public AddNewTaskToTaskListCommand(Task.TaskName taskName, Task.TaskType taskType, + Optional originalTaskUri) { this.taskName = taskName; this.taskType = taskType; + this.originalTaskUri = originalTaskUri; + this.validateSelf(); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/RetrieveTaskFromTaskListUseCase.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/RetrieveTaskFromTaskListUseCase.java index 40afc1d..cf7d787 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/RetrieveTaskFromTaskListUseCase.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/RetrieveTaskFromTaskListUseCase.java @@ -5,5 +5,5 @@ import ch.unisg.tapastasks.tasks.domain.Task; import java.util.Optional; public interface RetrieveTaskFromTaskListUseCase { - Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery command); + Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEvent.java new file mode 100644 index 0000000..c58d034 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEvent.java @@ -0,0 +1,25 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task; +import lombok.Getter; +import lombok.Value; + +import javax.validation.constraints.NotNull; +import java.util.Optional; + +@Value +public class TaskAssignedEvent extends SelfValidating { + @NotNull + private final Task.TaskId taskId; + + @Getter + private final Optional serviceProvider; + + public TaskAssignedEvent(Task.TaskId taskId, Optional serviceProvider) { + this.taskId = taskId; + this.serviceProvider = serviceProvider; + + this.validateSelf(); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEventHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEventHandler.java new file mode 100644 index 0000000..67f78dd --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskAssignedEventHandler.java @@ -0,0 +1,8 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface TaskAssignedEventHandler { + + Task handleTaskAssigned(TaskAssignedEvent taskStartedEvent); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java new file mode 100644 index 0000000..7ed9c84 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java @@ -0,0 +1,34 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task.*; +import lombok.Getter; +import lombok.Value; + +import javax.validation.constraints.NotNull; +import java.util.Optional; + +@Value +public class TaskExecutedEvent extends SelfValidating { + @NotNull + private final TaskId taskId; + + @Getter + private final Optional serviceProvider; + + @Getter + private final Optional outputData; + + public TaskExecutedEvent(TaskId taskId, Optional serviceProvider, + Optional outputData) { + this.taskId = taskId; + + this.serviceProvider = serviceProvider; + this.outputData = outputData; + + this.validateSelf(); + } + + + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEventHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEventHandler.java new file mode 100644 index 0000000..c1a18dc --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEventHandler.java @@ -0,0 +1,8 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface TaskExecutedEventHandler { + + Task handleTaskExecuted(TaskExecutedEvent taskExecutedEvent); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEvent.java new file mode 100644 index 0000000..8fad698 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEvent.java @@ -0,0 +1,28 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task; +import lombok.Getter; +import lombok.Value; + +import javax.validation.constraints.NotNull; +import java.util.Optional; + +@Value +public class TaskStartedEvent extends SelfValidating { + @NotNull + private final Task.TaskId taskId; + + @Getter + private final Optional serviceProvider; + + public TaskStartedEvent(Task.TaskId taskId, Optional serviceProvider) { + this.taskId = taskId; + this.serviceProvider = serviceProvider; + + this.validateSelf(); + } + + + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEventHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEventHandler.java new file mode 100644 index 0000000..0da730e --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskStartedEventHandler.java @@ -0,0 +1,8 @@ +package ch.unisg.tapastasks.tasks.application.port.in; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface TaskStartedEventHandler { + + Task handleTaskStarted(TaskStartedEvent taskStartedEvent); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 24f68d0..70818b1 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -21,7 +21,13 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase @Override public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) { TaskList taskList = TaskList.getTapasTaskList(); - Task newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); + + Task newTask = (command.getOriginalTaskUri().isPresent()) ? + // Create a delegated task that points back to the original task + taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), + command.getTaskType(), command.getOriginalTaskUri().get()) + // Create an original task + : taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); //Here we are using the application service to emit the domain event to the outside of the bounded context. //This event should be considered as a light-weight "integration event" to communicate with other services. diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java index 46043b0..fd6aea5 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java @@ -15,8 +15,8 @@ import java.util.Optional; @Transactional public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase { @Override - public Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery command) { + public Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query) { TaskList taskList = TaskList.getTapasTaskList(); - return taskList.retrieveTaskById(command.getTaskId()); + return taskList.retrieveTaskById(query.getTaskId()); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index 3decd1f..ebe9d1c 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -8,7 +8,7 @@ import java.util.UUID; /**This is a domain entity**/ public class Task { - public enum State { + public enum Status { OPEN, ASSIGNED, RUNNING, EXECUTED } @@ -27,39 +27,85 @@ public class Task { @Getter public TaskResult taskResult; // same as above + // private final OriginalTaskUri originalTaskUri; + + // @Getter @Setter + // private TaskStatus taskStatus; + + // @Getter @Setter + // private ServiceProvider provider; + + // @Getter @Setter + // private InputData inputData; + + // @Getter @Setter + // private OutputData outputData; + + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) { + this.taskId = new TaskId(UUID.randomUUID().toString()); - public Task(TaskName taskName, TaskType taskType) { this.taskName = taskName; this.taskType = taskType; this.taskState = new TaskState(State.OPEN); this.taskId = new TaskId(UUID.randomUUID().toString()); this.taskResult = new TaskResult(""); + // this.originalTaskUri = taskUri; + + // this.taskStatus = new TaskStatus(Status.OPEN); + + // this.inputData = null; + // this.outputData = null; } protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { //This is a simple debug message to see that the request has reached the right method in the core System.out.println("New Task: " + name.getValue() + " " + type.getValue()); - return new Task(name,type); + return new Task(name, type, null); + } + + protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, + OriginalTaskUri originalTaskUri) { + return new Task(name, type, originalTaskUri); } @Value public static class TaskId { - private String value; + String value; } @Value public static class TaskName { - private String value; - } - - @Value - public static class TaskState { - private State value; + String value; } @Value public static class TaskType { - private String value; + String value; + } + + @Value + public static class OriginalTaskUri { + String value; + } + + @Value + public static class TaskStatus { + Status value; + } + + @Value + public static class ServiceProvider { + String value; + } + + @Value + public static class InputData { + String value; + } + + @Value + public static class OutputData { + String value; } @Value diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java index ccdc59a..7a4e70f 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java @@ -3,7 +3,6 @@ package ch.unisg.tapastasks.tasks.domain; import lombok.Getter; import lombok.Value; -import javax.swing.text.html.Option; import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -34,14 +33,27 @@ public class TaskList { //Only the aggregate root is allowed to create new tasks and add them to the task list. //Note: Here we could add some sophisticated invariants/business rules that the aggregate root checks public Task addNewTaskWithNameAndType(Task.TaskName name, Task.TaskType type) { - Task newTask = Task.createTaskWithNameAndType(name,type); - listOfTasks.value.add(newTask); - //This is a simple debug message to see that the task list is growing with each new request - System.out.println("Number of tasks: "+listOfTasks.value.size()); + Task newTask = Task.createTaskWithNameAndType(name, type); + this.addNewTaskToList(newTask); + + return newTask; + } + + public Task addNewTaskWithNameAndTypeAndOriginalTaskUri(Task.TaskName name, Task.TaskType type, + Task.OriginalTaskUri originalTaskUri) { + Task newTask = Task.createTaskWithNameAndTypeAndOriginalTaskUri(name, type, originalTaskUri); + this.addNewTaskToList(newTask); + + return newTask; + } + + private void addNewTaskToList(Task newTask) { //Here we would also publish a domain event to other entities in the core interested in this event. //However, we skip this here as it makes the core even more complex (e.g., we have to implement a light-weight //domain event publisher and subscribers (see "Implementing Domain-Driven Design by V. Vernon, pp. 296ff). - return newTask; + listOfTasks.value.add(newTask); + //This is a simple debug message to see that the task list is growing with each new request + System.out.println("Number of tasks: " + listOfTasks.value.size()); } public Optional retrieveTaskById(Task.TaskId id) { @@ -63,6 +75,41 @@ public class TaskList { } return Optional.empty(); + // public Task changeTaskStatusToAssigned(Task.TaskId id, Optional serviceProvider) + // throws TaskNotFoundException { + // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.ASSIGNED), serviceProvider, Optional.empty()); + // } + + // public Task changeTaskStatusToRunning(Task.TaskId id, Optional serviceProvider) + // throws TaskNotFoundException { + // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.RUNNING), serviceProvider, Optional.empty()); + // } + + // public Task changeTaskStatusToExecuted(Task.TaskId id, Optional serviceProvider, + // Optional outputData) throws TaskNotFoundException { + // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.EXECUTED), serviceProvider, outputData); + // } + + // private Task changeTaskStatus(Task.TaskId id, Task.TaskStatus status, Optional serviceProvider, + // Optional outputData) { + // Optional taskOpt = retrieveTaskById(id); + + // if (taskOpt.isEmpty()) { + // throw new TaskNotFoundException(); + // } + + // Task task = taskOpt.get(); + // task.setTaskStatus(status); + + // if (serviceProvider.isPresent()) { + // task.setProvider(serviceProvider.get()); + // } + + // if (outputData.isPresent()) { + // task.setOutputData(outputData.get()); + // } + + // return task; } @Value @@ -74,5 +121,4 @@ public class TaskList { public static class ListOfTasks { private List value; } - } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskNotFoundException.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskNotFoundException.java new file mode 100644 index 0000000..830b934 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskNotFoundException.java @@ -0,0 +1,3 @@ +package ch.unisg.tapastasks.tasks.domain; + +public class TaskNotFoundException extends RuntimeException { } diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index 4d360de..fe25873 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1 +1,2 @@ server.port=8081 +baseuri=https://tapas-tasks.86-119-34-23.nip.io/ -- 2.45.1 From 67524548385fe56e1889008e3785c61a0ce0576b Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 27 Oct 2021 13:15:29 +0200 Subject: [PATCH 37/94] added common lib and added service uri's to properties file --- assignment/pom.xml | 6 + .../in/web/ApplyForTaskController.java | 2 +- .../in/web/WebControllerExceptionHandler.java | 18 +- ...llExecutorInExecutorPoolByTypeAdapter.java | 15 +- .../out/web/PublishNewTaskEventAdapter.java | 20 +- .../web/PublishTaskAssignedEventAdapter.java | 9 +- .../web/PublishTaskCompletedEventAdapter.java | 9 +- .../port/in/ApplyForTaskCommand.java | 16 +- .../application/port/in/NewTaskCommand.java | 2 +- .../port/in/TaskCompletedCommand.java | 2 +- .../service/ApplyForTaskService.java | 2 +- .../application/service/NewTaskService.java | 4 - .../assignment/domain/ExecutorInfo.java | 9 +- .../assignment/assignment/domain/Roster.java | 7 +- .../assignment/domain/RosterItem.java | 14 +- .../domain/valueobject/IP4Adress.java | 23 - .../assignment/domain/valueobject/Port.java | 17 - .../common/exception/InvalidIP4Exception.java | 7 - .../exception/PortOutOfRangeException.java | 7 - .../src/main/resources/application.properties | 4 + .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ++++ common/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes common/.mvn/wrapper/maven-wrapper.properties | 2 + common/mvnw | 310 +++++++++ common/mvnw.cmd | 182 +++++ common/pom.xml | 72 ++ .../common/exception/ErrorResponse.java | 2 +- .../InvalidExecutorURIException.java | 7 + .../common/validation}/SelfValidating.java | 2 +- .../unisg/common/valueobject/ExecutorURI.java | 18 + diagram_1.bpmn | 653 ------------------ doc/workflow.bpmn | 337 +++++++++ doc/workflow.png | Bin 0 -> 836841 bytes executor-base/pom.xml | 6 + .../executorBase/Executor1Application.java | 13 - .../executorBase/common/SelfValidating.java | 30 - .../in/web/TaskAvailableController.java | 8 +- .../web/ExecutionFinishedEventAdapter.java | 15 +- .../adapter/out/web/GetAssignmentAdapter.java | 23 +- .../out/web/NotifyExecutorPoolAdapter.java | 21 +- .../port/in/TaskAvailableCommand.java | 7 +- .../port/in/TaskAvailableUseCase.java | 2 +- .../port/out/ExecutionFinishedEventPort.java | 4 +- .../port/out/GetAssignmentPort.java | 9 +- .../port/out/NotifyExecutorPoolPort.java | 7 +- .../service/NotifyExecutorPoolService.java | 11 +- .../service/TaskAvailableService.java | 6 +- .../domain/ExecutionFinishedEvent.java | 6 +- .../executor/domain/ExecutorBase.java | 38 +- .../executor/domain/ExecutorStatus.java | 2 +- .../executor/domain/ExecutorType.java | 2 +- .../executorBase/executor/domain/Task.java | 2 +- .../src/main/resources/application.properties | 5 + .../Executor1ApplicationTests.java | 13 - .../in/web/TaskAvailableController.java | 6 +- .../application/port/out/UserToRobotPort.java | 2 +- .../service/TaskAvailableService.java | 8 +- .../executor1/executor/domain/Executor.java | 4 +- .../in/web/TaskAvailableController.java | 6 +- .../service/TaskAvailableService.java | 6 +- .../executor2/executor/domain/Executor.java | 5 +- 61 files changed, 1231 insertions(+), 931 deletions(-) delete mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java delete mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java delete mode 100644 assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java delete mode 100644 assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java create mode 100644 common/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 common/.mvn/wrapper/maven-wrapper.jar create mode 100644 common/.mvn/wrapper/maven-wrapper.properties create mode 100755 common/mvnw create mode 100644 common/mvnw.cmd create mode 100644 common/pom.xml rename {assignment/src/main/java/ch/unisg/assignment => common/src/main/java/ch/unisg}/common/exception/ErrorResponse.java (84%) create mode 100644 common/src/main/java/ch/unisg/common/exception/InvalidExecutorURIException.java rename {assignment/src/main/java/ch/unisg/assignment/common => common/src/main/java/ch/unisg/common/validation}/SelfValidating.java (95%) create mode 100644 common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java delete mode 100644 diagram_1.bpmn create mode 100644 doc/workflow.bpmn create mode 100644 doc/workflow.png delete mode 100644 executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java delete mode 100644 executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java delete mode 100644 executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java diff --git a/assignment/pom.xml b/assignment/pom.xml index 99996b8..b4650de 100644 --- a/assignment/pom.xml +++ b/assignment/pom.xml @@ -62,6 +62,12 @@ 20210307 + + ch.unisg + common + 0.0.1-SNAPSHOT + + diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java index 1d0111d..c77f6f9 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java @@ -21,7 +21,7 @@ public class ApplyForTaskController { public Task applyForTask(@RequestBody ExecutorInfo executorInfo) { ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(), - executorInfo.getIp(), executorInfo.getPort()); + executorInfo.getExecutorURI()); return applyForTaskUseCase.applyForTask(command); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java index 08a0895..99ad2a5 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java @@ -5,27 +5,17 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import ch.unisg.assignment.common.exception.ErrorResponse; -import ch.unisg.assignment.common.exception.InvalidIP4Exception; -import ch.unisg.assignment.common.exception.PortOutOfRangeException; +import ch.unisg.common.exception.ErrorResponse; +import ch.unisg.common.exception.InvalidExecutorURIException; @ControllerAdvice public class WebControllerExceptionHandler { - @ExceptionHandler(PortOutOfRangeException.class) - public ResponseEntity handleException(PortOutOfRangeException e){ + @ExceptionHandler(InvalidExecutorURIException.class) + public ResponseEntity handleException(InvalidExecutorURIException e){ ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage()); return new ResponseEntity<>(error, error.getHttpStatus()); } - - @ExceptionHandler(InvalidIP4Exception.class) - public ResponseEntity handleException(InvalidIP4Exception e){ - - ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage()); - return new ResponseEntity<>(error, error.getHttpStatus()); - - } - } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java index 4163a53..0a91805 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java @@ -9,7 +9,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONArray; -import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -21,9 +21,11 @@ import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; @Primary public class GetAllExecutorInExecutorPoolByTypeAdapter implements GetAllExecutorInExecutorPoolByTypePort { + @Value("${executor-pool.url}") + private String server; + @Override public boolean doesExecutorTypeExist(ExecutorType type) { - String server = "http://127.0.0.1:8083"; Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); @@ -37,17 +39,18 @@ public class GetAllExecutorInExecutorPoolByTypeAdapter implements GetAllExecutor try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == HttpStatus.OK.value()) { - JSONArray jsonArray = new JSONArray(response.body().toString()); + JSONArray jsonArray = new JSONArray(response.body()); if (jsonArray.length() > 0) { return true; } } - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } return false; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java index 5007c1c..db3de1c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -8,6 +8,7 @@ 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; @@ -18,8 +19,11 @@ import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; @Primary public class PublishNewTaskEventAdapter implements NewTaskEventPort { - String server = "http://127.0.0.1:8084"; - String server2 = "http://127.0.0.1:8085"; + @Value("${executor1.url}") + private String server; + + @Value("${executor2.url}") + private String server2; Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); @@ -35,10 +39,11 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { try { client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } HttpClient client2 = HttpClient.newHttpClient(); @@ -49,11 +54,12 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { try { - client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { + client2.send(request2, HttpResponse.BodyHandlers.ofString()); + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java index 56cb803..209525e 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -9,6 +9,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -19,7 +20,8 @@ import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; @Primary public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { - String server = "http://127.0.0.1:8081"; + @Value("${task-list.url}") + private String server; Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName()); @@ -40,10 +42,11 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { try { client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java index f9f2833..6bd56a0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -9,6 +9,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -19,7 +20,8 @@ import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; @Primary public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort { - String server = "http://127.0.0.1:8081"; + @Value("${task-list.url}") + private String server; Logger logger = Logger.getLogger(PublishTaskCompletedEventAdapter.class.getName()); @@ -42,10 +44,11 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort try { client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java index df36d58..bdc16d9 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java @@ -3,9 +3,8 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; -import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; -import ch.unisg.assignment.assignment.domain.valueobject.Port; -import ch.unisg.assignment.common.SelfValidating; +import ch.unisg.common.validation.SelfValidating; +import ch.unisg.common.valueobject.ExecutorURI; import lombok.EqualsAndHashCode; import lombok.Value; @@ -17,16 +16,11 @@ public class ApplyForTaskCommand extends SelfValidating{ private final ExecutorType taskType; @NotNull - private final IP4Adress executorIP; + private final ExecutorURI executorURI; - - @NotNull - private final Port executorPort; - - public ApplyForTaskCommand(ExecutorType taskType, IP4Adress executorIP, Port executorPort) { + public ApplyForTaskCommand(ExecutorType taskType, ExecutorURI executorURI) { this.taskType = taskType; - this.executorIP = executorIP; - this.executorPort = executorPort; + this.executorURI = executorURI; this.validateSelf(); } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java index ab6838e..f06798b 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java @@ -3,7 +3,7 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; -import ch.unisg.assignment.common.SelfValidating; +import ch.unisg.common.validation.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java index b0af2b4..08dc8eb 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java @@ -2,7 +2,7 @@ package ch.unisg.assignment.assignment.application.port.in; import javax.validation.constraints.NotNull; -import ch.unisg.assignment.common.SelfValidating; +import ch.unisg.common.validation.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java index 0593a30..5ba1901 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java @@ -22,7 +22,7 @@ public class ApplyForTaskService implements ApplyForTaskUseCase { @Override public Task applyForTask(ApplyForTaskCommand command) { Task task = Roster.getInstance().assignTaskToExecutor(command.getTaskType(), - command.getExecutorIP(), command.getExecutorPort()); + command.getExecutorURI()); if (task != null) { taskAssignedEventPort.publishTaskAssignedEvent(new TaskAssignedEvent(task.getTaskID())); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java index 7d7de5c..8f60789 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java @@ -1,8 +1,5 @@ package ch.unisg.assignment.assignment.application.service; -import java.util.Arrays; -import java.util.List; - import javax.transaction.Transactional; import org.springframework.stereotype.Component; @@ -27,7 +24,6 @@ public class NewTaskService implements NewTaskUseCase { @Override public boolean addNewTaskToQueue(NewTaskCommand command) { - // TODO Get availableTaskTypes from executor pool if (!getAllExecutorInExecutorPoolByTypePort.doesExecutorTypeExist(command.getTaskType())) { return false; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java index 6b19dcc..58b47dc 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java @@ -1,19 +1,14 @@ package ch.unisg.assignment.assignment.domain; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; -import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; -import ch.unisg.assignment.assignment.domain.valueobject.Port; +import ch.unisg.common.valueobject.ExecutorURI; import lombok.Getter; import lombok.Setter; public class ExecutorInfo { @Getter @Setter - private IP4Adress ip; - - @Getter - @Setter - private Port port; + private ExecutorURI executorURI; @Getter @Setter diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java index 521a748..560d7fc 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java @@ -5,8 +5,7 @@ import java.util.Arrays; import java.util.HashMap; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; -import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; -import ch.unisg.assignment.assignment.domain.valueobject.Port; +import ch.unisg.common.valueobject.ExecutorURI; public class Roster { @@ -30,7 +29,7 @@ public class Roster { } } - public Task assignTaskToExecutor(ExecutorType taskType, IP4Adress executorIP, Port executorPort) { + public Task assignTaskToExecutor(ExecutorType taskType, ExecutorURI executorURI) { if (!queues.containsKey(taskType.getValue())) { return null; } @@ -41,7 +40,7 @@ public class Roster { Task task = queues.get(taskType.getValue()).remove(0); rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(), - task.getTaskType().getValue(), executorIP, executorPort)); + task.getTaskType().getValue(), executorURI)); return task; } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java index 2c3bb52..b405f44 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java @@ -1,7 +1,6 @@ package ch.unisg.assignment.assignment.domain; -import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress; -import ch.unisg.assignment.assignment.domain.valueobject.Port; +import ch.unisg.common.valueobject.ExecutorURI; import lombok.Getter; public class RosterItem { @@ -13,17 +12,12 @@ public class RosterItem { private String taskType; @Getter - private IP4Adress executorIP; + private ExecutorURI executorURI; - @Getter - private Port executorPort; - - - public RosterItem(String taskID, String taskType, IP4Adress executorIP, Port executorPort) { + public RosterItem(String taskID, String taskType, ExecutorURI executorURI) { this.taskID = taskID; this.taskType = taskType; - this.executorIP = executorIP; - this.executorPort = executorPort; + this.executorURI = executorURI; } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java deleted file mode 100644 index cd23b6b..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/IP4Adress.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.unisg.assignment.assignment.domain.valueobject; - -import ch.unisg.assignment.common.exception.InvalidIP4Exception; -import lombok.Value; - -@Value -public class IP4Adress { - private String value; - - public IP4Adress(String ip4) throws InvalidIP4Exception { - if (ip4.equalsIgnoreCase("localhost") || - ip4.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { - this.value = ip4; - } else { - throw new InvalidIP4Exception(); - } - } -} - - - - - diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java deleted file mode 100644 index a66dbbd..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/Port.java +++ /dev/null @@ -1,17 +0,0 @@ -package ch.unisg.assignment.assignment.domain.valueobject; - -import ch.unisg.assignment.common.exception.PortOutOfRangeException; -import lombok.Value; - -@Value -public class Port { - private int value; - - public Port(int port) throws PortOutOfRangeException { - if (1024 <= port && port <= 65535) { - this.value = port; - } else { - throw new PortOutOfRangeException(); - } - } -} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java b/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java deleted file mode 100644 index fecbfcb..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/common/exception/InvalidIP4Exception.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.unisg.assignment.common.exception; - -public class InvalidIP4Exception extends Exception { - public InvalidIP4Exception() { - super("IP4 is invalid"); - } -} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java b/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java deleted file mode 100644 index 2772256..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/common/exception/PortOutOfRangeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.unisg.assignment.common.exception; - -public class PortOutOfRangeException extends Exception { - public PortOutOfRangeException() { - super("Port is out of available range (1024-65535)"); - } -} diff --git a/assignment/src/main/resources/application.properties b/assignment/src/main/resources/application.properties index 3cf12af..dc443ab 100644 --- a/assignment/src/main/resources/application.properties +++ b/assignment/src/main/resources/application.properties @@ -1 +1,5 @@ server.port=8082 +executor-pool.url=http://127.0.0.1:8083 +executor1.url=http://127.0.0.1:8084 +executor2.url=http://127.0.0.1:8085 +task-list.url=http://127.0.0.1:8081 diff --git a/common/.mvn/wrapper/MavenWrapperDownloader.java b/common/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/common/.mvn/wrapper/MavenWrapperDownloader.java @@ -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(); + } + +} diff --git a/common/.mvn/wrapper/maven-wrapper.jar b/common/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/common/.mvn/wrapper/maven-wrapper.properties b/common/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..abd303b --- /dev/null +++ b/common/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/common/mvnw b/common/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/common/mvnw @@ -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 "$@" diff --git a/common/mvnw.cmd b/common/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/common/mvnw.cmd @@ -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% diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..f4e8342 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + + ch.unisg + common + 0.0.1-SNAPSHOT + + common + + http://www.example.com + + + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + javax.validation + validation-api + 1.1.0.Final + + + + javax.transaction + javax.transaction-api + 1.2 + + + + org.json + json + 20210307 + + + + + diff --git a/assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java b/common/src/main/java/ch/unisg/common/exception/ErrorResponse.java similarity index 84% rename from assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java rename to common/src/main/java/ch/unisg/common/exception/ErrorResponse.java index 2fb834e..aeef41c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/common/exception/ErrorResponse.java +++ b/common/src/main/java/ch/unisg/common/exception/ErrorResponse.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.common.exception; +package ch.unisg.common.exception; import org.springframework.http.HttpStatus; diff --git a/common/src/main/java/ch/unisg/common/exception/InvalidExecutorURIException.java b/common/src/main/java/ch/unisg/common/exception/InvalidExecutorURIException.java new file mode 100644 index 0000000..1d619e9 --- /dev/null +++ b/common/src/main/java/ch/unisg/common/exception/InvalidExecutorURIException.java @@ -0,0 +1,7 @@ +package ch.unisg.common.exception; + +public class InvalidExecutorURIException extends Exception { + public InvalidExecutorURIException() { + super("URI is invalid"); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java b/common/src/main/java/ch/unisg/common/validation/SelfValidating.java similarity index 95% rename from assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java rename to common/src/main/java/ch/unisg/common/validation/SelfValidating.java index a8d366f..bb2d0fe 100644 --- a/assignment/src/main/java/ch/unisg/assignment/common/SelfValidating.java +++ b/common/src/main/java/ch/unisg/common/validation/SelfValidating.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.common; +package ch.unisg.common.validation; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; diff --git a/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java new file mode 100644 index 0000000..fc6b62d --- /dev/null +++ b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java @@ -0,0 +1,18 @@ +package ch.unisg.common.valueobject; + +import ch.unisg.common.exception.InvalidExecutorURIException; +import lombok.Value; + +@Value +public class ExecutorURI { + private String value; + + public ExecutorURI(String uri) throws InvalidExecutorURIException { + if (uri.equalsIgnoreCase("localhost") || + uri.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { + this.value = uri; + } else { + throw new InvalidExecutorURIException(); + } + } +} diff --git a/diagram_1.bpmn b/diagram_1.bpmn deleted file mode 100644 index 313a860..0000000 --- a/diagram_1.bpmn +++ /dev/null @@ -1,653 +0,0 @@ - - - - - - - - - - - - - - Gateway_1vd3as7 - Activity_1jd11bs - Gateway_1e4ckdq - Activity_1dl4fvt - Gateway_1e3rabp - Event_1lm6x5y - Event_1ysgenb - Event_00rvb9o - Activity_0u3tts0 - Activity_0vs4eam - Activity_0mwpp9o - Event_0v7hm2z - Activity_0paecdb - Activity_0srcl99 - Activity_0assw9c - - - Activity_0jai885 - StartEvent_1 - - - - Flow_1hc51tx - Flow_1sajzlx - Flow_1ijfkpz - - - Flow_1sajzlx - Flow_0cpd5ad - - - Flow_0cpd5ad - Flow_0kwvrmc - Flow_1w3uh2m - - - Flow_0kwvrmc - Flow_0vpcut0 - - - Flow_0vpcut0 - Flow_179e0hl - Flow_12nh0g5 - - - Flow_179e0hl - - - Flow_0366zqm - - - Flow_1n8jm89 - - - Flow_1w3uh2m - Flow_1n8jm89 - - - Flow_19dbo28 - Flow_1rwgf2n - - - Flow_19dbo28 - - - Flow_1ijfkpz - Flow_1gvdy5x - - - Flow_1gvdy5x - Flow_1yxp4e8 - - - Flow_1yxp4e8 - - - Flow_1rwgf2n - Flow_1hc51tx - - - - Flow_12nh0g5 - Flow_0366zqm - - - - - - - - - - - - - - - - - - - - Flow_119ldsr - - - Flow_1mm9swr - - - Flow_119ldsr - Flow_1mm9swr - - - - - - - - Event_0v73rhy - Activity_0n8uuvk - - - Activity_06xjrrk - - - Activity_0uxkytf - Event_01nh9j2 - Activity_1qgjnyh - - - - Flow_17d0j42 - - - Flow_17d0j42 - Flow_0opy5tp - - - Flow_0opy5tp - Flow_0sudw7l - - - Flow_0sudw7l - Flow_02uzxx3 - - - Flow_0rpv16j - - - Flow_02uzxx3 - Flow_0rpv16j - - - - - - - - - - - Event_1oz3tr5 - Activity_0xk9kck - - - Gateway_079742h - Activity_0umieiz - Event_062x6a9 - Activity_0b6bh6v - Event_1achffx - Activity_1mme68o - - - - Flow_0od6iot - - - Flow_0od6iot - Flow_0k07ofo - - - Flow_0k07ofo - Flow_1fwmda3 - Flow_050yku1 - - - Flow_1fwmda3 - Flow_15lkkxa - - - Flow_15lkkxa - - - Flow_050yku1 - Flow_1lgcq8d - - - Flow_0oqra8s - - - Flow_1lgcq8d - Flow_0oqra8s - - - - - - - - - - - - - Event_1b2jt5y - Activity_1ikvmpl - Event_0y66dbe - Activity_11lqrhg - Activity_1oownmu - - - Activity_1ohwol1 - Event_0bbqq3x - - - Activity_1e9hb9h - Event_0l7ljcr - - - - Flow_046hsk4 - - - Flow_046hsk4 - Flow_0zoqmub - - - Flow_0nyxv8e - - - Flow_0zoqmub - Flow_0ip5x3i - Flow_1bqiz65 - Flow_142xsjm - - - Flow_1bqiz65 - Flow_0znvsoe - - - Flow_142xsjm - Flow_0ue2i8v - - - Flow_0ue2i8v - - - Flow_0znvsoe - - - Flow_0ip5x3i - Flow_0nyxv8e - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/workflow.bpmn b/doc/workflow.bpmn new file mode 100644 index 0000000..6d68a24 --- /dev/null +++ b/doc/workflow.bpmn @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Flow_1rie16h + Flow_159tlyd + Flow_01pbz6s + + + Flow_159tlyd + + + Flow_01pbz6s + + + Flow_1urp3d2 + + + Flow_1urp3d2 + Flow_1oy6e8u + Flow_1rxws1j + + + Flow_1oy6e8u + + + Flow_1rxws1j + + + + + Flow_1rie16h + + + + + + + + + + + + + + + Flow_0nuuhk7 + + + Flow_0nuuhk7 + Flow_197gie6 + Flow_0ruufha + + + Flow_197gie6 + + + + Flow_0ruufha + Flow_1duwugb + + + + Flow_19m4xhk + + + Flow_19m4xhk + Flow_0b4g73l + Flow_1duwugb + + + Flow_0b4g73l + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/workflow.png b/doc/workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..49466939aa20738e5423de482de84e821241f043 GIT binary patch literal 836841 zcmeFaXIPZk)+Ve7k_;$Okf?$Ph=70~QB+i-A~_U-WC4+!K^qg%Mo`HHK#7ub5G5#5 zh2)HYQsf{x&)V31-Z|$x)6+d&o_FT@{8yKuc%HpixYxbzwYaCMEWeY2kz&i1Ej!Pj zJAG-(7J6~`7(=!l{=|>>mDZLmY+KHsmc8s^FxIX6QvK^P>1P!u2Ilv7cWIi0$exxZ zZRCB%#(r~`W*85J;4{Ve!eK*)u#2(|uYcv>zxiP#VdjiH$F~rZ5Z|wb=g4mc)R1u> ztF4QQq}y2ErEi}$S7PPS8-*ikFWNuAJh`yCT_nv*xiiZQ7xU%LmaRm@WK?W2K0ow> zCz+y}L(iRX1@a&Mqu_0ZC=QD+aQr8J5?g(q&$F4*_gz2u{Jvk2&oleUA9_vyumn%a z!{U>XA9w!a4}RnaxU5}2FYEu@0e@cB-}b(rp7nojeke55=as*>k4;pHY+2aW{kL!9 z&%`*HeMIi_V^5|y?qj(W@n`=NNz4&~V){wxPU;_>E#XAX&&Ygoz1Hn}D0k|Zi(%i2-Fdd}$N%6yTKK^l4hC;lX!dxJROfR1cyuSj>3g@- z>J0u##l8L1dpy3Bn*Im;NCc zb1bLPMn4L(8xYy^M)UeLp1nUlrCj}PNHN?|H3ECy$WK^_{0Cx#Hqjo>*8AAn5BYYH zcR%x-$ff-cxQyU;L&DK0e|dcO^MM87$iL_Jf5yP#c)v$M6O*odrPEWi`#bOd2VCaK zcSACx;JTrBdg{SSgwmgR*1z{}RKveV!AwD$E>4a`iXiJV{{fd#{BB4>e$-;{Lu)+i zIDdR>IIQqJ3Tp2YwSmm`Go2Flo`0s(9~zc^rc=V2>%TFqC7ZT(D@#?fM%iQC4Ex=!J@}HIJ zkF+V7pOxwl4T(Q1)t{9rLCT$(6xhZ;G;?mFmAa6v8>t&r0<#c->#- z6Mt5!KNObztW93sDUk)TcWl2A>fk5?Bmh^|t{g=)4 zpCh@}Wp|7>r6d|H1sko+Wj30I)~n<(>>>?N$Z{tL?cN%sCEd;g8s z{dJz;C)xWW!{Gm)t5c)xzP(;6e5ty5M?Ke<@D?lc5t?V+<>Ewftoc8yji* zMS>D_WJJUrRF9=R-68~x&x(JmIZ(8*f=fRTbb^D~{-eO~SQbv%9*BOXbUIm{mz9YmQDjM`p{K#kdKD%ey3W~xU^yWlG`sc6r zH)mw*h=_FaY{uy#)Afr)QZZ#x)?N8_L~329e9GA<8-mOC^37g z;9zbIF&)6RLD~2swdqcNTJv%@}ws%T$AF$I{evPkgC<3Xxov`?l)v^Ii|#rY|d{ieC+M< z+w&&RnNbCM=(SaA+U#tjiZdlvUS8flQ25RkUv}!p!d4o2Qy!n5ov~P+9hP%)t3LFn zA9eDP7Sr!qfPWn0zx`k?2anA(5_8^gig*^k!yrNVG}wayiHFMkk+0%J#PeLA-5XW} zQhq<%$nR&{6xZ2gCl1{s%9Skm<)Ex)^>;sYI=W9Kk^BaWL0u11h!)m;UPE3KRyxSO4Z3Z5`E(X_c?++CR=%ysa+Lpwb zefNTMg@@c^PB^tW6R>popy+$E)5Oy6!@7EImh+?+|7ue_U6l>qUK-<7LC*D}z#gB_ zo@JJZ?WJm%9sPW3_k>k~KRMN5`Ogcz-YRh7=BqhZ-rml(=`PH+?#wNvlbWrUYqZBn zK1wSbNh+PN7a&-)-~@;3p(G-X0ON4sM+X+C34T{{p&+RII{9ME{TmL+$=9y=!p#H$MiIYn$<;w4dP;Bv62g2Hg8-Lhq`ueC3?$O&n-Sl6NBOp z{8RVtjqUT_vaYpfI&7>hEZlfyMX=jE4&i3nh>GplK6v@iPxkz9n%m{d7rq#N8KNQX zfN0YmUW^at1)GyReg@C#F(q3#*P_c*tAFwgK3Owe*?VKnc^T)uE;PSOVq z@2NMWjor?@CB3Q}+~U2t*18d5_JankUWB7O+t}}xU zSTX#I=#$PO2EE*0Fgcd$g;N!j?h$wnc;^7Fsp)FgL_bRUgvrvCwCM)9-IB+_x6Oth zswwZYeb`H=bjn}^7b>^E^f|Tpd5HW9Q!EIlu)*ugzOLHK_FBakyYOYFXje$CO?QV& zpwwA@TpXI+LnmSKNI$&CZ8SAw;qz#EnQ~%`o8?06$&J#PmFU2>kMmyAD*Vs&h~D2cOYhd|)A zXAzFA$OLa&x@0==)qe2f`O%itQIm%Ri$Aw7!^MYepKK%6p{tK=ys?Ev1>QUWJX6Ss z@r;iQ4nDaj-00gv_S@?URPPV}8`O!(6lLOO>?C19g6F!@ zsf8|k6`fL#C1TDZKLWX`VLi!wYpf8QT=tc>+tPrWpDf{l^CkRJ12LUuu zzYu^&IR{bqm$t6UPf>;`r;E1jEtO<;AImTn*jQhg9FDuSi*W@%eII+0zQ?gufB5Mg zmXV%DZpZP5MY+Gf>NJVdct^9Rt@BvhCzok?R?mk=i>E54Negbm`-D<0E)E1QESc2> z>=SB$<9eK+5-W9&D?f~2FN;u>&NHlBoH7`+i{xv zGxWzwgEu<7A)$8=5*yEMY|zAb&ZT>}af^GePFFWt=XJM~*a99Lew7$=ADeIIb$Tjw z|2w&AT9>hm>W1;W-X523Y88nG;k?>{6FcZ3Ua;LMzfa54&=9Y{Brmx<^xSyE^W!wH z5TR1#2z`C5n)hvG8ADa{niqb>%KDVQMzA3FUGyWF0=h)nJe2LZGVdKckOQyC`nakP3;XNb6BJ#GM& z=pMDu)U!L|HlEX(U``p#F5^?aL7Z0n?ZfQyX!>NO03iHqlUh20)f~iCC{ON#J3`^- zyhR%_{}On$vAXxjNdf_bzOtKt==9sWd9VLXI>jG)AF{jm_)1}URvgFax(nX`2-~^8^`2yIJ zu8Wl}n||_}Uq5N^ln5Y@Va6$fIhqU_@esY`jgvNe-018?L~KbjC8=#}>BM=g4X%## zWbYMe{qK(FY!I$|aVbG5vT(94{*ggMkJrk0hO>F`vl%E*PjtKDqm5gGgMt!5mBn9$ zIUoD|ya-BOIF#AX3z_llLJ1Vj(Yo&@Sf zof57|T&k9u!3C~q8|$miyzn&4krKsAU!wN&#RYz42A09*Kc=? zj+S=O%M-`C%g=CfU2oAgiw|THoe+KjR;O=YCmhN&VFT8sc*$^Xq}k%lO%it{v6+uT zjr14~f%n5Ue+-3?5XnRH~<+TqV0Jcu0}M z{4otn)!jI@yHjM($nM7NI2H2boQ#ajo5Qy_h{^03b`ZImTwoI!bD?wRQBLQ|T(O-}G=)JSf zN9H8u36)%X$CJ)B+F$gYPuIKZ2qGl5x>zk8G!S$$t(SK8y^5D0R(v#NaW+x9d$J@j zpWVMg)TnFurLy7tSbN4|zrSYj(r{vIP_WnR^Jd{MN8t>w9-R6pgnRdaCtWXN4hTER z+dG-UFXAHpJmvDoUT$u(bxAB$kJ$e3E@KzOg!&F5Y-p-J>+=IL<*p5k<|@9t z-_lNU4i#ISFP`tPn*I{1Lbo9S7A$bJCPd3Rr`c$B=##*WFEth3^je0#;o4`kX-Z8v z&DiW3l|(DUb#2<^I7p%rBtn^mX>0bLpA$NQ`p@PwNbT`NH zC%~Wh;mu|$2if7PkDo4g&1C;5&Msr<1Mjwtc-Ezs{OSYeZaZ1Lr@G@rS3yRbk-y{V zK3$t%nI+;_S3i?ZcTald-6@BN%=pN_&4RS{vG`V^)Pt`(ZHg*ioDX%|G5ia)@?GN~ zv4pVOmb`y_$bVG#{xfy9b8kp&Bb#|Q6!~oM_}lttj=`A;(t?#e)EpotYdQ}vq8t)o zv2Uv^e%s;7vGy$EZ(rWqeGSvB@}oCk@aHde32w;+e#7O<19=W>7R>s$N5*x+t@~wy z=iHC`SG3wX=rF!4mnCJUF^h3E4K_uwcyT_jOtOAZ65;tmvehqK()Z+ciM^25C1r6j zTpcr$PF7%L1Y|>yNL2I4XEn;Bxz?QAAqhSyrxv?XvxSgu#lM%h{GNfF>UvD}_6{XB z|Kib&$y>Xn77e|gW-`>T2F0i2j~0z(wgH~@&s!b0_4apc(N3&BkepuXrWh+#vN#8! zQ*4*?>J-Cqb5**xNriRACp0D5MZ?L*N>?VPBYRg9?tCDj*Q0WuDB$7)tH}OE#_fEK zf%|x~G4DWia4;NR{-G*!DneamAT3;`mTk!%wqxEz-|Po*b!za{=D+yNTs`n`4iKpf zNfa09k)b6G>Kcy8!tq;JHLayj@~rwAwdX@QE8%S~&F4H^FyOBDNQ`#6PxG*c$?E{! zc{)GaclmP{qsv7)uTiTdC=T1rz?sl@TIcCI2Il+2CGDeBjFAwl3S#bj{wOuCM?4M6 z$^iFP3Bn{q9~mABaI(rI2I{Ru-6UV|TGZ&Z^x+0uQUf3CVFM~BiYxWD)8<>H-omm1 zd7)(-DD0MU3y%0iRWK`NevZZ=hUimz!C-_7ln}L2$ut2yHI-p!`k2JLJm>gc_uXcy zLbl5y+HF_OoH1R+G=Q?a0$CS8y6JYDS8lbI)XWpyyQWuax<&!pf!QR;+tq7<+Xc?G zFl{B8;bN0ETkn!M172~K?1YNQDQ#9_vK^PpU2nwQjIk9)QL$5eX!C9mv!qoG=6a%Z zrF*hUu$H&c%sRbfzN5`JSkeeO5PmDSceAnp6@8ao?)GsLrK=Yg6BJJ;(vnmK?4u5r z_;xNB$Ko;d=DzG2xBy%I4&OU0Ziid+?CV(cy;lcVgNGXvMUBrb6KZ>uc_z3XC%w!9 zF|=FQq!W*DitYgxhZ};U(v?>3twh2UCsbO<2Is@+k!f1r!s6>9=_&cLRWn{vb14Tv zHJ35M(F=B$@n$;{&1Biye5Y}!lE|y&CAXf(XZl9Pq^x%;8WwyF_lM~DO8$V588(1c zL+grRpgW@09QV3F7s~kE-93^zx=!sT1JDpjQR9ywSda;Y_mfbs)gpIDgHPDhk|)$` z^~)%)VGbNPD8p9a+Hk~Nbn^;DhE@ykG5%p1IBYvGxk51rqE^>B_T`sXQ1b7GIzl=4c8|wQoz}IC%I!VAbdW$*NqvDT z*I1PjcbWR^vNE1GT7Qd^AYdD8{2M2mG?y){kO5INH{6`is7(to*%!HTS3y=s!3#vu zM?Xun5A(#W4aT17aqQe4-TS-4yrz&^daqK|yRp6yq4^GBj1wa-pHtwNZ`82cbyy@e zcb%1Fsd+Xv$M8sG;+?x4HLtyXv)ylbH>;F{f!lC=L~BJxH477v zN2mGrx5XX~9XgFd8hUn>Mr*N*t6o-;*{G))onQI_LyqVM(a6aS zraT##2Rc@n!1PaI_U@f+AWFEm8>%g(dSfO*zI(imZN0qHBVd|fhiL_Jj zxjV&G{G|Ku9_VjUQ{hYV-L_leSdjGkqSlS~hurn)Q9q>X`rDluqiIESat(KOF*AZI zlo?NbexszV0+5xP`jf?(t#diYfa>%2Sv$fXCa6ppTq{DDs6tqAA03p!q?^a z5D7-Pb;X`8sRP0hd{T!`*TQA!MhhTi#=7+1(}LJjI}&!Pwrp+Owz|VAo%z7ofV;pU zi<~lY z_~{INWsLiTSg&xWReDK(YuWmohcqUbH0!X3WL13NGTBR8&;2>4d}QX~3&#TI2x9wi zkD)#i_rK?XT%Wb1tWn!}F#j|#e@83htHfjv!Ix9+I_<47lFajjnr(~p+OW!iZtt>2 zoz<0!U&!sgz2~ZaedVpCS8mL1$k}c|D!SXKReODxTqCylR*|=*Gn%Dx5y(hbz(~G4 zy-;<|M`ka0B`HExN?;;ZEtM}}0X#Q^#v|WD;~rVO#U50F*+E zZU>BewT@M4^WIS||BAeK?{++6iz}rg+DhcQ=QZYQ{H3b%gorEfYyoh#RVPOMw)pzO zF>becNdgCPmEF)AdrCcW^HbRe_*92Z*IBXNRr6C1*;o1q>!f9=`0C``LJ1rVE<-Mi%8K$mz0NyEIDR`Mc2fqz@ z=N4^xsIShHd#Tf(4FvCun}DoT7-|^;Jq~c6eQzvtxlxn}q7=8i@O68e1)U%3=MV`4 z`K@!Q2NDv%)-IQOg>k1>5ojA^0d__c`vstZUX zg72D-10vYYX6VhbB}Sf{%KRm`*V8WXS}jQ3yh9QL&zhev@ls%d?JS;45sm1w%BO-> zq6F^E-B|KZlP-S@I=&rb3+7O17Ste$<})lC8C#B&gc6;{&>K2$4EweceI=Ub;dvH~ zf>6P3aaNIQTA;E>)WRbgrJ=Q9?w*PV2QbGPkA#nTF3&CkQ)BQb*V6hRAPMp93uat# zrS0vOPqz3j!wZj$z7i&owe^sIo0r_IQi-z%yNXt`5;`Qqh5=*yUKD4-L53#xdfDTQ zB=J&1#V3LLDc-AawBYK~<>zMu@9z^bZ8AC31-*Q~&$Fg_khk{D)SH<=-V*%p&0E^_ zu3^nI4hDxFc3RTa^s_qE-I_ueAc^WVM&0)d9f6LI6oA-JoKfTv-zbPWHw>M5 zlZ@f4b#M^E^oW2!iJo6M#Ky%L#h3)=vJ?)+twqQzotyzH!;jl0bMS#2FmPH9c;c zdT2i+eSA}gA~_XsB5PupIYAwwXkM`7FC%%qU0It}Yg;C`5OtBxZ<08#_~S*-nM>RQ z2W85=Om-5;hsYjM)I5L9@=ul{u2jC>WkD;7AzFgS^EJ=uY>(P^uO{W;^RK`gb}$Q_ zo*RFBDe|yy7rFz?DMGEsZc+!dsxKxCfciEZ`wlx{2w=Wj0a=jx;4cypGafN4vpk%K z6{JHM+p&J>ER!`2Vt~e=ghQq1Xm$PjLkl)kqKVxR>iYlTPEp8;w^s@2~09afLV&0Ktbwh|h-6jp{` zY934n+Ik!SQ3O3t%Ii~UEobQbaI`X!yEQyJi^~(_;{qWlSYK9DBTy;aBnv0XOeG8` zP!7I%=am+6@JUO$*V3(ugYYThAe`vlVjH17zh!BQGr1j>$%`GWrF6QsC62s>L(iVj zB*z4r{$GQBT4eE1ODQQn5sYsYKISk~-v$jz;92JHB1X`p^#Z_!_V-So%bW7H`P+OT zz5zz_!J=;mfmSAmJOJDiU?{BMBhw8=<;c~{2In}!Vd$Mk`TQ*4A|Ht0TWbopj|@^CE@~~nwAg+)?Vxi}#*=Rc z@PHf;)fD0bLJ5=6>KP!T1=_VxZYA1A{~EKiOO+DNpym_=2fri{fGDS+x`9uThkz=Y z$d8+W_E7?r&3ZCs?Fb9VfDEj}{xzg&3-ixLg2{@rM?WHenNITGOhhp6aaac|{SKHb zYI*~gE2xuD(qcPQ{}dQS_jkZf>;q=VCTpbYc`q+TuVAPUmF#>AoNmFMskOtR@qz?W zOh6una`P8$E+{u;q1+@rt%S&+Z2%s&zl;Dfh#C+DX9}bBufOGwM8zk2@v+`s>|cu0 zNBuFYMlgdRmC^0gS!v|Y*Ye`qe2+DlM!|WfDW9giTmH5q#5Xv>HniXDctjZ62+2Sh z&sP{2&u;J#$LCE{DB~T0jK`B$1y-U8w!xm3z;qPRi2sFmj?h^G!!H0t3WKPPUP^Yu zg`NDSe9V9(be5fMbBRVGQ`1|>sb0oHGV%)7;UEm@lYI-G*6HOlF{+^*aFS+C?Yv;V z@H51`g@ufWq5%zzG=0d!7X!j(rgtc9HE=((oCO`G>Nwe3H!?0|9`-oO*~5P8a_=tg zY>U0-a53skG@ob)5D`#Taij^6zbLP$w1+L^6_mqj27v&c4NIYbnNh;a0>p(1z!Eyx zg0u*bovvR39T_Y15HKD~wU{92lSnjjn0#^p)Mymd5hcBKE?QbtWe;1u3q0mP-5h~_ zlM3hpTxq`R0^)$zFhMnYoG?6Osy6*|4w<+_U>}mc)7`qH(ca@$7=Ms39-r^^-e>~} z)_@%Avg*-RIe5xLrz3aicy?yLPB;^{6SAPS)@3yU-SiF_8J5Z|QxX>d+rl^R{MrIl zKXDRZTTP{}@awZ8&{5vl>VVPwotBRj<%cGeABz7OWH8tec!pj-JME0cX=A9P_YAbi zI|5e`9a;n^t`v-NN%$BMK~e$qOrdNwkGEui`05F?Vajg!-t7M+4-eu(K@Dw_Fr&3r zedq3ckVfAqU_pk--r-O`(wX2V%tvH>7~byfvxvQf$ySD2fN0P@I8;Fb^814)HVl!l3|wK?vK=| zJim1{G!0x2g+uqqNVqzPnF;*edQ>AX_<_mAMZHtm!*R`dN|aVQ#2paDns1*GK17%s zFW&->xnm3NIka@ZgPK)sqEU<062g4*WgF;|ya$MkTCk4D(ete9U4QKbI6aUMX>)|G z4^%Vv_`Rn69r-RCV61&M708e$l?27?NK_XzJ!Zh#>(YJq5QqfOL3II-`dckvnH9j< zJ^#?z%Rs14NP50=t=kr2QNV3asz&GVN+F9#v{C_?htmwFp24baT2%=j!yDEi23L-F8U zi~8VZV`!H+5-=M>uYI}}v!2=7zk^x)IGw)3Cog~JYG&tmmdU9tlb+gLA!qus7!pPB zD$tDERBIeJfSYr%mn2Z*L`NGPv}Sc1Fa$uYXrt!j|AOL&A{W@cBgE0I1BJiiYRQPC zf{UYgCE&gD_F;slva9%TSNTu{x1Zf{~@yp=vNks4Y2z1rE_CoC^ zTPdPWBnH6rYq8I5z#*N`0qo4p<~$oW4k5r=vmq!(^?U1#OF4=GHOEFW&6UN6UubzP zeI0GkNDYMUjZXFF^)ccm(UZD-F#Ll7i}aMtIZKdIkE*5!P)D(5{UW(T5M&N}OX~!v ztrY-1r@QUr^HrvU!@M!qX*+>x>s{iuj}C#cbyg-Y4?Zm&S3P6TR=*F97W*2C~p#muK+d~HoM_g$*^!xui z+eJ{CX$V^li2@a0U!p5Sr`r4ffu96`V{v>albqS7bW=tiDu3~3A34wa$m{}?BgPxS zPCcXw7IKHKR4FkUN}iW-GtRx9qMRx*`;lHpVtsL7dgysD-8#tTkJ?QX;OWBapvK%E zyV8Y641>V6w%6fW7XVir;xwfq0e+t9pIBA`JipIh>nj`I#BNO8k$$(8biXRuiAk^c zxEZ7~gNZj*O5WEp(*uY!CMjDL++=dv_M;I^H^sgfn;@&Wv zFmQl%>0G;~^!jx0K)u{<>+7#-iy%`fMTcN(iJMw){sdX42Dd4` zSbN8ST37;v(Py6TIdJwJYBLME&oyffK!f72R46vupEvWIlJ!;388sD2S|AIZo<{EW zUVQ5(eH3IqA$e;_jiV;(o|FiVNe!){*2eQ-sX~?Zw73}uEiKotVeVb-qp5jS04X}M z=brpvu8Ymql?II)Dn=aV#!9V>p&bIT=X9-e)b|7ZnX$e+y5QQ&B{hBTv52)!U(D=B zn3Ah!@F>ZCSWe37K?NYq#A8G)HtDfRc(Y+-v#rL=`^@g*aZPO|WJxt$!*v@Yp!$p4~vzndNI^Z5e*S zg*UN{8;KY?rKl4E35riHdDsjDiUysW$bW9Z11)w@YeDe|c4)B!z9o3fl!^Iv7)@cf z0a@Yi8`w-S4)S}XW#t6rbjJN)GVyecOwUPoQ2X}7lurQ2lldCNI{`>C3AV$_hP*ea zKOX~~l!Wy2sNzVG>6tf^#kcdtD~oj=w~DD75iO!^TnwLq^DsCU?7FFhfb($bw5Bfu zDdT-%FPKOL@n*~6RY^}t&zQ7^xS81nU?%x@i4VnPir#)DS zf7b&1@8LIuy>B7%(=cgQy->PPasmwC$g>@w5JM%?BDf^=Lbo%f(mfZ3E%RYQ7)g92 zrRW`c9_O?ql_*Av@HH?F6*AH~^rU#{=Im+?;Mu*REEolmmov#2C~m~lShFdTcEX!?71y!)0J-&y+R?bA{vi5;W_)9l}}3A zqT$6UHaDR(;F1Ddhq5w#mUZB_<$eK$LD-`U`v}D`3J|&0JU_8E+lXQy-?sC8gYwLd zm&afdJ>%V8g@>LZjw4NrAai0xt0mf7)wZjDcsAliE^hl#$Ew)?6t-pWfF;-3-=iej zO%rKb>N@z=TBGcV3h&!p-(F)(PIxK-JrQR@@-0`IQXFT>?f9WRK^sn|tUV=sFed_n zkn1Hk>@7r9fl3qu5AKW}cEktYYU>z4<1AGi}AcH85lovWaU zczIq}79Xf>mD-_@b?N{_pX8RO5q3GtUg~xGyW}8XJJHm1u7j{4QoW7aEYh?tOMA^U zODCe$vS&Vd@cgjY?%xz>Wiaf~Zy&p&_cRTW_7YR&+f0PhIEPjPgs^uJz3YcZB+23u z&b#NKRrnoT6@ZUKZ6+m_75xSG5Ueg5P!GPZXSR~9Bull#~-)`5Cd;WS$T=060H`e`lexP zKv;8rMBL1+UE+taknJk$xuP8|?f0Dhq@>ODH$ujr3KKeF!TlhP3xd&^vK{?Eo&}&@ zn`IP?ULQFd%%bnK0U47qIW<>j+Ny*kx@-n}avByuR3lK~0;$*-hSY-vjH^x;mbHvR zTE2TcyqeWpVtsAWySm?>pGAQ$=W+@tBqgg_OU6ZeTn;p7H3$RnLs|pMjo6!AtdROp zABS>47Y3s!3-oJwvlNxyDl8f7mY9-3$zK2_;wm0*N^>FiP>#PSZDeQ&BN~*ReY$(D z(~A)|JKT7{8Z;6x5V9&k%5Qq7r$RU4! z)iR5Aq}agJhG8pT1%goJw3}=_%)4v4oLAM99_~6}hK)KE+PgX^Isg>*MBdWbDC;9T zE7~&_V#%YJSM+m*yAi5McwbO3uqhp6a)|ND$bcz-diwLrBsCX!QZKK%d&}a7?|hrB zjhpokasVvgXi|30{L2U;N%ohOF4L)Ecz*c-%`xOkyI?jG#ae$VAUhqR92(9Gza7hN zRGwbx-H?)6X~$LJr4|Yh+0C9^JUYVS;02>M@Ak;=--C38Jo8}{SZ^TAsyQRCHQ==_ z)i^UwKIB%r&mJ1aa_(t=+3Ht*%D(=vw1(+u=N?zTX;7@bd2mX+sDei!R$*KZVynrO ziLef|)_`*0B*0Ie`65Kb0EtP18U%__Co-<%V5jVM#p(wwSB8b_3tKvD zDduB=Grd9vLR0sroSypePZMC2)V6YY*Sk~}$H-n09oMpmUNP4oh!o>&aEk>OW%2Q5 z`9e4eAtw-}p2IiH{u*{cQ{0vrra@3@nl0dM*f#+~R6}#CSrCZmvGv1WMVpDEMVGIs ziHn(4ZzO)|#1UhI=DH67I%#&E*RcY@{-(S-vHy{mG1{as0OUA-+P(FgSQW9a4^P0h zD*uH2nY9rU-}<ZeY|J7l;&&+kh}ApXNPxkZ<+Dgwrglwad=S~=0o$B1|tW6 z1{}^Ce;>+Ko!e=nJ6#IttkN{nT4P83L}~^q8ep7|8Yp7@zHplI#f7qslIn5Wk~s~8 zxF1w$)`bx(!0ZPP&3P)EFjiC!Kfc@1BI|ZaxJKwwh8W=3RIy=)TDqF23vA{{S7u7p**QAnR{2PKfv6O(Z6m1UHCke|7C zMV_iwGoOQTz60GtfY?|XH?z;ZL*a*4+%N;WD7eRmY1}WGC`{(1krEEc%)9h>@DwHA zncMdQ;!9V`%(7I)*K$;`>)ZYu?I~pC=M@npqZ-g6IYXb*RvuzU^1=< za*EZ%oF{`T@%)~z3|GOW=!yE&JTIi9EI3_&^GutHhb2y;m-5ZN9WLSb>-PR~fR(#z zCe_xHr*yeFoo>C%z%eza*KK=r>)X_b<*SY=$rGB|b(%#$7u5Je#ep{^j&_!oo5&vs z5^$RxI``&lH%^s%Ork_9!QE>FM86EfW1^58GUL&t7k%kEv=$B^QTzScgoc7-ZL`SZ zGG^5y-kjH+QYMujLyjoTYSk%1vI&6VtAV0DcV{WXC(5>*vwa2g7I+HVvkI*my4LD0 zp}Nb5R4K$J*Xx8!9vm!je4(ugELV%(Y^NGf8x%jFjqa;a_xzk}Aot~>(F^Q-pJ-bLrmt404(^Ut-s16Hgr;NYT!^Kd@M5>qR|K70AQX`ukAkv=t10nZpdK z^j*}R8Y@Ku0Dfi)+sYg5N%vouY4;4P${S9Iq+1@MxEw2LYXC~9LulqR(<~i!{VW2e z*PG7)RKkN)B>ke6A?0ou^VjIt;ja1X7{ciILYk4fVpDUU@isE39G+hF43 zLxq@{K^ve!eS7^$P}QMnWqpqc*!C3!G=%W>-7I>mI)Y1B=Thp#s2dlitt=*9rNwJf zTv1`4)Yr6qVltOzm9y3&G*ld8BTS#VYj^gDm-?Mo7HBO%0Ucn-u8(nOU{MrCYYY1o z)Gzg^y>xZcRv6xw)$n8r#NloNw}d4hVXqH+L>U-1I(B<5bfac>SWXyB2|~%16__=! zx~BMfOe@w}&=xpc)6_7h-|Bc*9kOok!)cCoJH7;3=gc>Eu0akkFmA+_lQcPhz74}8 zaRR_CZK~E0RsFXcC?WLdzVpFqDSdDh-2S)w4ecb(MHHI!$(Og45Mqefc zZSWJLDOvZuy%Df{6(4?N-4hWB$jb!G>*MY#7>zW&%J_7aX-*6`F`)1?99BW7@&eS7 z>_A-808{Vc+4ONqSn%Md`Z0~CZVq${{)fV~p2_Q`m#w??^)%GiO~~T86(s^~VKZMP z9){DfnPOogQAACxFn}o-OT*UNDLyvd`w6DZS-53NN8MmSfiX05M(tK$+Rw!&7Y}LG$w2uxAAVMn>xGNit`X%W03_!F=aWRE@N}i*{ zJaO~_f6#e+FbSPj+*CXywxb$47V()lWMP|AIJ|xn3kghxvGI|Pxpq!b8wmG}W{rNe zE=$6tARCgZM`f zF3E!z^&CE($$+P)+k-Z3oEUx)DkwY5a?lG}MW-nar#r-7uWIm_PWPHiwuAbB-WH|s z{+a{9yu{v#K2tO=3ZRTw5T8JF@us_Y01o#D1^0`w^@)L^ui?7=NX1bpg2zTP$=%+; z?Unm>*GV+Sp}*?B5H||yjv^|L_Rcw^t+^Dg+1e~QRP4^S21mW$EXwX|tfM%r1?lIK zUOGP=gAhT4RhqV%H86v$RqyUm`{U5`Z!WmZed`wjJs=cEF?VrDUYZf~Pfom`FRJ{n z8{V$HdBe?@UyFBlC9SAOCnW9z$M~WFG>Cl`Fkh7Rz`V9vMK1s`oDZ!(|JV%(WPzxn znp%Jt<)4RjW&LJcF)l{!+ArOo-(hyrLR$}xdKI|WyJlfUut8z9n7U0Q*Uo{EoLP&G z#b!rdo;W&@^ z!2=li87o6#0(dSG5Nhl-htv)FdcjL~U0TB`GE`2EXDbd}02hYoFtlGyo62cHSw4`F z7Nl;6JzQa-K_IANbNcfy4J`tw+K-6y^nrDlXQE|YX{+8G^>r^KO-xL8+%4`6vW?n` zPer1o{r7pQVEBL%^AFJ4^UaUngWaZRJh#8M7jkn2gL~1Xp?)Zr_4i{I;$~%Ny~EQU&>Sg)y%Mw9WfRQmOpNVw zdL<(|)@5@$#M2XXl}M5s>kw44a6{9zGB&?KH4jkcOhf&+;Ci+IWKTx7c8VNMyEPCj zErlkS>DEgdl}}dEYU*8F9sBwS6BHk=lhi64ioH3k*yaeY#_#$1ff|+`FLBs^OyB0fB>ZV|o_1}_mpj{P5Li9x*Zq1Pl%HRDxLM)Eb%krPzct`l2$%3i_yMzV9oRWp+{p` zbqFzffu@IUQVb@!2GCj+=l$`bA0tK6sV3TKsW8jh6v@3%n;8Q?ai3;v{XwwD3tVpU z&cKwW8kzsi4Lk1^-vEk<1)4kFO6%OetOE4nk+Me%IR+zb4O-3)?-Pnvf@W5i87tD9 z&U%)Oc9_L__Cw0xSrsm+`3hN!XVqJL^ka-E6i zmnoDcF#(sZF3c_Jn1U{T;3jq^-whm>g}KA=<49*(m@q%lI2R~D$|5565G3cy7_t$i zM#w+7SuQ=F5lowy`<^rM9k_{9$gBC~YZhc*p%K7{Ht58zT?Pi>)H^k@Md%Cc2N!;6 zIkrX;tx26?ARY@uHmsLm+rE?W@_u%;6$;e+SI|D&3(Qa z-RUF8gf|6#twgS!vFT_=6E+(74V<}VQ0`4lb zMXpELRqgYf|Fo8p3#&YMcvl&M>VI@FdEuh3|Ba%dpXZCE~2x)ZUdniqME2H1+gji^DOkD}O>};CoM$`^3 z(U(s7J)1^z<){s0CD?0`7!46S?Xc7gDygq4A1Q&`sNQd{2s;bO*XB`c_py+fa$hOD z^a)heRBSwA)ST&$oc;LQQVwnj4EIp+zReJ+5Erq@Z>{0gns-8!Op16IDXxJ=+^2!c zVp@i^5ezuV1wCG%MKV4dNq%Q0z0!`B5Y+N!e;1;LWXfT<<=qYhq3s^22ca4JU9Ce9 z#YY{z`B#biG%`kK- z%Xqq40m~epmGnf5As0Fg7ML`EH)J^Jbgd2-CN=e~mY<$zL@myf(dv=e!dW$!_1}wS zuggm2SLqy66M83v;oG`7Qt_o}4c?6_V_7Ouz?PNtNKxf&rM)J_YV&o?QY!I_09bdn zA1Xcpe)B`OF#6e=FDVNHoGs2Wsd78hH7c}TVEPFl7Y@V)qnJ1Y6;iO8vRFJ$3eBo& z!F-ti!owI!1>AK|58Z>Ea{0Z};W3Scq|u_I*9OKGy=z6JrKwMxoG<#4T*wa9h#yGO{Gq`Fa zm&q11hR*OK0>&5q4NL#v>c z4xqW9?ZymiM*XH{)HU^m>44J8#G#hLCShm=T~Ol}A{Wwisx{ps}`><;^!`k7v%MNokYxa|D$C}z#_RCdAwF_x}B-T7~!{a zu@KhGX?=^waVpYl4gvecGHdcC3P-D8YqJ`)+3*5>)-Ya3b@)k2L+!Of=S4V<({5W8 zTOFa%Sq=MEFD{M(9-{}o-F7<5;r7_%5oTimWTw&9QOx)a1a{UnJRD>3P_x)*OpILK zCIEe8e+cxs!BUB&1JC7ykKRH)e8BIlhC6WBa%lV|ynB6h23HL(hgsE0mF;J; z&b|nk!3sEVsS&$9wDl_Wjek@6nz2F3gXP?nJ@pwbgq~b9x49QMBTAzumh%l{kxW~In%OPu!fuuCo0N2~;h|*_M57v$Hro(2 zOY|s>CpuK&ZAX!F3|7}7X!GZ7S_sbub^33Jc$32X2O{RXi3NJ_Ya7tF{Xl&jN%mZ> zdQ$2eZh&@etoB>9q-d+BhFbRMT~e3Zme1{9vD@$jOKKLZGue099;WG$q(4C8EkRN? zDEcU}-B>Dtjb^6TyDt05soSwN5O`VcaJ?Jvy-L&!O$xn1Em`T#V{)mP@nDZ;{St!i zWW5uGzi-G%H(u-ASnEBpI`!5+))wj*zmVF7@DbywlonWoZB9t+mql>8N zd5%4D^Pr&KIEwhHete{Lu67qp=X$nm_Jz?pHJ8sxZN9&20aTVZsFhDV?R9hNo`Q0x z>hqgh{tLFCHnn@ZQ*?IOgW@DB>RZgv+(#u?&f>zv(+96on@oqp&gdr(iv~W;o}{7J zfSU`iOLU0Zgj_&XRPu)bRoR(3ac90V3${v>DolC9{CMBocPxtuB zQ;91iedNr#c%CborWg1!w~{jL?|2|(6AvUWLX>``l=w?>o20EG}rr7 z4APxC(!c3!8{j0+7{;$M$@q&vKvHHvv+~h{RKR6^aWyasJ9pv94F{w%%Ln?PcH#+i zDm?~;V!2mP6!IjAPS3)M+rcyxZ71qErMn*L-p11?7k0D4iW!3e_yIGC?5iaX0ceQl zP11@Z&}r2m$I+Vq3K0i&KT-f>n_T$@>n802SfxvitYcuE%DJ=pOVYy$psdft>1bFE zDPWc_bwN#78N3eb?`S(x9pdLTK|j(jZ*k2cK))5ya_4473Q_wMM#dDNoz#>Q;nm2N z3zOof^1WNbZsOJQEf#o4KH36t|S-8dLYJXeCc-)*btVBp$HtFnGm&*{ckM zIbqZB^tbmzH_T73@q*?DOGWLMP+NMmSEE`5t?rOy8D?>a!7>f=7S?e1&*qDMeVL%x zoR+-euiemY(f0a^2~HC91Q{(lR&(Z~@sA9SM0sAo>2}}xv`MFp6=Wu@Jt=R`kSfw= zSx8>~sK+yY7$opm&-x`Ok9r0oZ%`bShA%J%=izmy)?cpbS*vqEn0EkfgHM+qt@dJF zpSn|-9`A(mm#Fn-??mZrwIp%JtI}b=ZNrt@=8794?=>*p*BL${sg=k|(Xg8|ZIJwh zs6hg2n$+AYl}dmOUB$k$4fe}V(?&^i)_V+^Z`_&`i39u6>F!u0M9l#(*p;eAj-?V{ z9WCO-cVkgzKvWcikf|a<(gLt4{FJ#91*HV=cWzx9s5^veITlR1@VVmjxEXS4&UY|e zr#5B`l-1#1EFYPg#q*{@Y)X^QH+4Jg7t#w&4-?}QM-Q;c#M^2zE)>N_OG@W+(kmf; zGeS}gf?tPF7(yD_RfOrjg>A&cGQNA(x$OyKEzsBzM+&h`0Etl%Gkg{NM97S;Oa%5P z$KlN0M_@g0iA>$a02h>q0r3CWpNvNidx8^Q%YT&gFR8aPJl)Gdy$l2 zv8`H4W7VBvhtidPxjVZ8VWk>-xQ^wknoYKLdsvIwNZD+JgzF9VBEHmQ8dJAgzPxu3 zID6h^02wFQZFy9OnWg3}5b_q5n}j@d&A|F9JjEIDMo0nrCOyNv;aGoA5E$@geH5IA zHb5oNk-8_06WHKF72S?S&o$IuIiio=SMt>w{K|-g>s9A8KlH62wtyRL7}z5{7Rx?X}#N6d2VO?3{4*R)g(T?b93KuUo#ezrurd!2W*BX$V5>Zx_^R z81WaZ%SCB4dR`v|s^7s>mRpMNgsn4^fYIqbJ{z=!uDt{;UhAvBz8QECj#+Tw!91ta z26niqZLYCQ=47*Gz3axDle{Mh3xxa%e?G*uRq&TLb{^0j-|Nt0Nh`oYm;!i5U_Bo9D3{;$NvIe7K9AApJXxQ0 zigz~vP>`vYs#D#HsdpV&xqBe!E?~ACx{8XDu$yg-t$7eR&dEl0r(Z=YDk?z@Qd?2g z=*cZN2KO41e(g1CHv7d*PV~Jr+r?4j|pv+?%l z+E1YBhvPe#t_2)sK=v)&ArKJqFgWY_7YKHp-P@_U@b39cH#<6As|Ljdnu ze&1~YXwofxQ|`UfxQ{*B;)s42(0mgLW=Rfbmj_rk+MaLFxUBbF8|PKU5yrQ@$X_2E4S8yw?BI|3ohb6Xt&F{TNJcC#hRFrnC`vK3Fw zsW>Funbg8IT?)kc{mmX35<98;bNMMJT%Um7Ns;Zc0o(@_QpHYB<^-jKFsjMr!`BX2 zU$DF%&+oldd#c{~!~OHE02Ap%dp+!f%`W-O!>_hrMf>K}!R9Bqr#xZbu6j3SH~}(T z234^XAjqVSbNkFt9$~+ny%N?^4S}t3V3hkM&?~n59wj}FO4d+76;A#RPS&t9K>h}! z0LBp10gRWhs!E<0>190Aw`@$a>qx}rwoS?}uHIgt7op^TF)TiMnKBwnHoP(e3*wqb z51wX)eMJ>L$~Va_)XPJcxDtej5m&ivr5IgMUn zT+YS>TC<#;B-8WlF#PfkCZu40A%ia^;#MnP+E~SHykoz!e=oQ6i*&1!xfXv|WRZ8h z7PKBr&^Vr$SqClaOl5sd&o?VbIfbWOmVgd9k{h=CnJ*!^!#QyL5n8852OD$ldt!m2ey!2ab)ob8 zfG6yhzmIFmN_+Lmaz$xD3c8aS0PSg84Teu&`s}V_i?9tRCSF#O6+P%sdl?D3J7ed#?cfX)-Mr(f`7|=XSLuAJy z$HNAvNY~u+17LpZzqc#^`#+Bhb3(gM@E{}rb-i5Q_%;33wS`{R;YhpqglgXF_}Ecd$t0FE0Jwi&Ja41Sw~8EI;mlu=nQiSodAKa9or$QK=-Qq|8FdtifE! zn7P3anaWURD$OLbGF4=ZGRxRp$SD~zlQ_-dG-PZazlQs{?|1M0?&saN=YH=$o`0^7 z&vm&@zw`I~uC{kG-FYUqsYPEP)M@dh;Ao(Z-Nd{n%>t(BFuCAKs>_$QGX>;fdf9@uOb`~!02aY zKHk$dzF%*X9Q}=0g2W6)aW;`5t+73{C#Gi}Wz1JZ*#<)>P7)p4en0q8Fr1e*p^%eH z&7$fLmhVz6Ig|EOVS9XW{X&g_$`CJ3coemXof`~J-_IO1(`i_k8QS~`P>Xeq?3@E; zGfQi3Xifw){9b)kMmtO2EiT56BI2`oTRFu`r%St44V5^f<=LMW=DZh3Ke(beYHq4M zGjYj8+OV;Trf+sQNR?mT*%nTu9pfAQEQxZNQe*0EnVHk~KRCG{{RAOWeJ*drnV5f@mp{fIFg5o^o z$4^G7Q|-j2S(LK0)F(D1Q^+J$BcrHno5q^2L<4^U(%v_u{#AF% zUpJVOCDfw!ov1y?oYGXArmqDmSdDVzZj8W(3q6sRW@sJr8u{dKrZ<?2QKKmv+s8v?>)+O^ucA4bf$G3H+$K>2{-)Zc+*m^?TuOz^xJ~`_9W?1 z<<*Hv?k96=8y25Fj%`9=?OoY%b*XycP)lB)F*R*yD|RgS($_S-&dIB=*7-qAbw5iTj=O#O;!PdB%Q}f3=>uK#CQ-Rod;CjA_BSgwaUyHh*ar&MY)6s1&Eic5C`^Nk~yGjpfcByb=jJ&AwXUgWD_?CbIpOx&Y{d2IU5#7Ih?m_6$A0= z7V#fjc6Q{>ZCMtoP@(-}WgoE& zhTVW0LT98btF~CRK68F{*S;nH#lg?ifrAcVw-+lGiROm9y9U`gb24fCb}!aXPpb_sl5R*cx|PJbUJeBk4(7` zeG4^t_NHAt6U(}QYK|!QZ|-i$=e|rR0Ru7Y$IaGWQMzv16I_kRPLEoT*{Kox+^6oW z+)qkih>^n_6E9XWrN_82It@37K44rl>-uG=J_l)9!eYAuGfzib!2j?yN?1QZSyQ>Y zJhU59ojYI-%JGmj=2?}ovZe;vr|&!4IR(fm;Cu$x;Ht1m(e8^aZt#nvd~3<5EN{iX zA5QmZvupOdk*`Y`uZ0z@Ay56_ZD917cW43Htgt){c-Zv^=Vv$ z|7+eNbwLA7Ibnf=T&#X;-XGXho9ffte=jtS(pq@F0$USLoYk^7*Y-~1HONkil6KeC zzL%e$@06RU9vjADm;Zdu$nSP)p3JVDJ0oJ5xHJ#l6}7bmpVWwi@o9pAXgWtWP&K6t!=&Dd3QhFtDym;g}xl z7JiW}-SBAhBhTqeiJl6lQf1eNa~^$hx+dLNGE(GBSVDoD{$&aFx(Q-M6H9|$M4s_P z)V{M1lFG&*)!VnT(zWOY?VcvbP8CY8VyuQ2 zO*j}T!zj1Jw~RPmmbFe>zqdVDMl-mfqbSobWZ?i;+$mW}iC!ZncH0X>STPchy~Y;N zgcCz~Gcm!gKZ{!@<$GE;62TqZw?rp2@0UoPA6}_Hd#z=uSap{K+QQ>eH?Qc)$;_7*H|C}oA;hRKACu+2Layxn7he}jr<`2tda&7OY3g#NYP2Pu(Gt2)_KcqR;(5+(X&Z4 zoIezXp5U14&f4EDhy+RW?a|Jje%t#oB5nC+A0pTawHnGJ)t}6u65FweL6i8X%lCbn z#~fcgq1hf;gJlfn*fKqacJE{7ENj#rp;>h74U7Gz=e>=fmyom0 zZ@gGF?Z2OGj%HuRi;P{BoU_ytlUZFzpO=2-Het#qvaa_}PrdyLaYx>@RE-wrfqK`+ zFQzGEf1h@fVmJMg9}wTh!`U`w82Oy<=k)N=`r^Vu+oEggeW|K$o6rg-ZZj2vtju(q zuoljiv|aw?%a?d8;q7^A^;be23#*QXTmJ;MwpAbnyq6vX*`YA?^WdN$kKBn3PU#1# zbdG<=ERV`r?dp7qhk!ESL#z*HbXNECg(WxO5_v9k5)4FM<^F=&bmQiH5!>p5yY1K( z{uQkzs$#Mt=`*)kNO>0cH}xk6U4z}24<)FC@=h3JS@<(XrSlgKB+rcR?rj-%-Xh_o zq!P|=o37?_bNl#(FQ@`SSXv>eG|2<8>b!oP!GXs*~5s0H4GqWTsgv#A`ZrB0@TgpZ#(< z$&$~RiBm(4ek1>u7+N!3x_l)cO1b=Nm*<*5K5v11p5>isP=kl>ZrFeoY^mBACapD- z&upH%cuHcDd8(Y2(3o6D)fU zBtWlhld*bTqU?ThN|;M}7o2qHaBJbcGdSoZbJGOc{`S(+B2?0Q-qJ){lJk7r7u)r| z&?&Q>7Vk7z*5kK?I&y`BiMidO6+{eOkG$l*hUH-zhIau=$T}{y`MZs1od(I*=peI4lQg-^t?m7`!LBOW*74L^RbF~?g&G_#MJh)BsNOr zWRhjIE7pDcFBwHI&%M0lKnnP+k70SrCE%Y3*Ad~Fp`WENjI48IV!mCg1eZ=qNC>Zf z&B-wIQ^GZC#w#&qcqVI5(C5`QrxxvJ{g~O9M>j1H#q4&QG1NWYgIx?6-XPhZEHUxZ z*y25(zDd?fjMun-Kt?}ci}=af9TPixrLx-jOB5Fuk}^015zaA6o2BQ*5^7zlDjpXa z0%gYF3_A5UTHt8NJ(nFRz7L9zev@wmD_bAU3qU z3A1f`Y$&FBZRX=c#e_rG*EaS`!hDc-cuszTJupkUq)ivk6kroJbAbEY_p_0Et%4dY zAV8}r_5QJPnK1|5G zTnWD5^~RWIBk)OK=px%>oa94!erZ_+HfV7X-DJlGw*e^L^b=fu5SRahTJCP7Mb30%1BX?p)$7v8if)cj!*5rKP?KNtB;?Q{jx`s$u ztq)I*RD|)FV-=tU3)ANqYy+~H7_c&ZsIaOr+bWpk74iw*E=xqM{|F>I*&d*r`(~b` z#wRZHUtCThC)jU7cN^$rDr@z{cWa*bU1^lELT-lZV| zrY0(3yjGulZM!&MAPU zehSL4O0hDYbNyO=zN8pwLzPf&bLRHgI7g9K0CrWpMd4_7g;o9ak9p3XJ*yB5rzMX3 zDR-8VZQuGV2Y%?SrD=?9mSh~go1X7Ta(L^+ulRdmp(I6a4*0^n{8N#uqki`3J0Am( zrWTxDJ=>`L&MC*bE*_bzX0e-dS1fXp{XxOO z{*z$ev?L{+`$Mo(^BBg@y6ZnV?(1sW^jteX-;MbNbUkKXp`yJ;{>4sxnx{%s!*~Uo zorju`G(@hgHnD3jDdq^7C*myrn$1LHXq+R-$64evfP0oy4@8E$5GaL)KOI%~zF0M6 zq6kvrgWUY@B=`}3j3aQ17XRkOm&0>!xG>}8%?eTxXr9{vYV11Qq+55_M)t{B^85XD;0P5N(uA*{~MmxISu=; zK6v}osvMkk^#q6RA*6dP?;)DrK6$q?mmDrh}8ZKJ;$KvMnX`PAe zM&X((pc{v%X#E*e?)(|7=?y9IoW0wA{@1-bxiHA=)E2k@Fa4({{ia(X0O2KwC2xMs z_-lhrIjZzb+#!#pr2*SqF;KD6UGK30$HYJb=}89uOT>wx7)pgo^>otFhhL%pD6h#! zLG})45|%SM+XpyQ!$Lf!$Cy1o{|03)83$>HQuoZ&o@p=feDGq})42SQUq_+|z}P^8 z&%H$lW7@>UJz=o)5hA`9jDFhp&^qGk(f|l-m$Os?7z+a!%l(|sbr@jG@?zD??9VZu zYK~r5#@j5SUau;=TyQgApVkRa%wk1WzdX+% zrwChME;waJ(w?(^_d-IxQr4WXdYPy*oBg~6dzwRW?;@O=GR*YT3ukBeWro``t|EiogN=((E0ABn-LPSJ{h4KRLQ+Oc$KD{Gwwqv#)U3I zXX<$=l1w2cd3MHK)ygx)2Nv(Bu9r{7eTU4?;vxd*RM!9J7DI1 zTY}zxc4yz7W>7vErTfX!ZsUQFGex97#x}YaZ%}=(_kw`SHB!tz#tC^!w736e5i_Kj zJ!Gv!wNMDrU(6LK-PtjQp2PR3b=nt9K~WtH`Ric_lumfboO-j;3ej;oOLmP5bVi{V z?(teEhMtF*YnD}{r<-oXwTeYFyG6`%#;LFR{-x>2nM<808}1??{j|Q5Q}$p>0YN>R zdxRmX)_Eax)&Ox-!<2IHp3#`9+FFgmU9unJds>| z5Y+zs{rhMa2RC>5g+dFkPPaw|jjs0FgJrMDFqOSo0SGABGM2d9{d>mWZ`+*LIQ>22 z*sCFO=sN3HTNvfaqT%|6)7)KjZON3I!LePgtE;MppOUv$@wjj4HDa%nB(p@5ROrYc= z@#5g%kk|fvp}(0?i)E;-*pOR0>3CakU7VxyDL{95zYB@Q+O{{Ccr4OL!s|_Pc=R*l zPv|~4w45Reen&!g|5VKLsTfCX@40QLvP|DG9?)Rr9)VR?059G{zUf-I9 z#)e-=4YpWOn|*2tw?~TLF3}y|{Hk`!5*4!Ci+V0_}L2UX`GzMsbvE zxGy2?*mFw?Q&2Ji16CT>E@+*~Fez?AYE)p_d`)3dD~YhL)r<~?UICW+=;hgsI~g$j zu_i&4D0Qw*j-n(smUy}&c;0*(h})$uGe53H7T~_d^Mr?U?=`K}7dXXY^{Bc&Qo-xR z9rdB;v)f&Ew*7S&^B-Q`dNOwP^*Iox4^|$2vFn%TyLaPZ77MFigShET)m;=Y4DTPH zRaR2k6R6ELg{rPF4Ul9Eqe)CeL~o8U=rObExczHcSa|+Kq@kI~As(oQOS%a(!0a8k zMaCn4CkKb?kk&+9uB~BTU26E(5BJ23?>%!K*nFa`Uvqyww?=D&2)H?B@pJLhIrP-B zUUT`_;lqB0rTa&o?(+d?RcPNeCtS?NXp52=gf>MSrOjuqpD~hd6xgz|I7C#+ zbZ7{z2-*?oNN z8qk?Yo{?}HPr|W$?oHH8PaA#-Z&$7GC(RP&i&h&rgb$%sZs0s9JEYd#7oM zhXMIfi#DsvP!k8BPk0Eojtapq&t;U_N%SXg@I>r;7z5ZC3bvPpzl2}1{!Y^r;IjGD zaI3+<-N$#I-agk;nVhrXes2_odwu_{>x=7|E@$}Mqi{xT4ct^xcq5YbU|D)x z_3_HObio!6lSXqTSy-jdYOu|$DR&wZjY$G(-T_F1oM!L+1037tEa5s9zn)v0^JTS> z?6dbS>sb9rlVpANp?$F_LR5qsvG?1#ccF4!8{i+km{5q042T2u7l?Tjn-}3wh2H)mzw1HU#^zOWRb*KQ^kU#niVbu>f zHC_Rs%VPPXMfBUn4idbc*9r%j*(2tvFXdvEIm@o;^YU6gE^fla#vn_gUOFZ*tpJa5&tINdgADnH5wRy# zztH8eHu(HzPug=2170i>+BTe{=QGnZpllC+sqRBjiRF`CWNuMyy~C-kugiS@>c;r7 z_fL!<(TEvGbgNM?H}Hfft4UPfrmUhe=H^Y*6QEPEIR1>){eYjv8Wz#R8~6-%zJLE- zrcnESd+@F!LN7`^f2IPU7vBMMd*&WJK}HoL1#t8z2(iwbXx90p4m&3P^mL4dk_+QA z>~T(mz-aS8EGYSg5%B1|rXHhyPao~Sw3OKv!n*DEL~S#(^_b02d)Om03;Aj{T6TrU z-ed1fXPu6?TC7Yfoeycy0KyGK$Cq?*Q!ZRuB0rDO0%v)k*_gq}TMjZKbcLsJg^<4UK7q=5~pH1_L*- zHWtq2JyOKVRgX-*Jv5mkTDJSzofsvk$vT%e)F)KbfOnPXEwX9MeDhqwA30>U}__KC1E*YV4yifRlLDx|H99U2wfRIQrlHN+_dMo z5Bea5UOo%hETS?)s3DF<>Ow%5^y>BN>6~ra-?BF!^kZClE_ulaC|tATo2W;sfO`DY zn&th&>$<=H1anQyRm9tXV-u56w|+3*^f;PyNh0Rz#p$`AXz~Tg`HK@<0CdcW3;aydVJV5`qWvIzS@M5<12b$Y1^Ct3{KA}PD4O8e7?B0REfA4t_L^o zCwoyo?zu@V;q^3YAMM?rh7Cc?^y@?H;4T)iQc@;DoJ9noUWiJ8df{eyzDhBatn5hA z#k>YTC1Sq7JN7p}exOv_4T0(5jW-@QH6&{(@|zTK%Fd4mC+%?}r9usrd13U6%}4^_ zGYRoVfhp~llQumn|iZx2dd0Z=;LMniMMOOI1S`w$e{hRCQr`<5>f) z5?SO9&;_o8Pv4qh_(B+Y68f@DB#MzQ&WX0?ZEGh7>Pm$>dW}NQqDK0l?NHc9XM}!Y z+MPLEYu{Z)r>!QXEAQ-OVJ-c9=j%%Ya8e#bIX-biB@Lj=BAzncGAc3h(If_Irn}K@ ze8$hTH$z*I&P&M3Vx-;g#-=_Q#mF9IWNN&&SW2;u{Z$iX1|&fOh?GWd=aIHzb5t{W zicu{LMow-BD#fzT9vPUzrU#aBxz9+BRq%sB5N)@sEcb55!AT+i>wEDEl$(8ZobQRK&72u4 zwXZayj%X%`XU!E1AR}euK01|XVr_~>y0#=gat=y5eV?B`V^Zr= z$Q!+hlV0)0@rm1pp_+u7&j-MP{%4qo6^1EBW5Y1=qCP(QLTZHK>3+9^l^G9ZFmu9LYpMO@)s3N)DEZ? zLsSIS7xmEP*!EG|RXzN+o9*n6g?UZz=Bk1&BW**%ryT$~YMz`pm{m-_tEKRKMiRqd znh~L^f}KU?wfq)sya#pJVE0G0ao*}uhT`q5eB7MU6EmpqcB2dHNJ9f~P&|T47@I9A z*Ja}2(kjm0Ie3bVOg1rYTyLSVdxy@FK)Wh1!h%h1SG;G}Ii(L1jei7J&^JS_!!}=VNuh4!(Md5&Cf^k~vvXo<1S1SJpE+4k+;dD&=Ky%a?U#hzUR=Z* z&q(AxvQ*d6&nHN|Oe+KYeL2n{wo}IKd*{_|*q(5+2Pz*mpf`Dkieb6$eW?Dr#b(RY zA^n>PR@yOr$L7m+r4KrxeubJ5XXLX}F2WP=6F0 zAM4Y-v`v}OcDO2Ni>z1C9g&`a8332B$%!#SrIWjR5%s#y{g|I}9~vV>2_VWTg{X#B zM2S0A#)cjR)Avi*HH~=|!f8*NBne)w(E@T=&4P30U1U^QwV$0IvGWph>eWy!u5oE8 z_4alJ;H$j7TmspL>avyokg8GLqiva!vJy^6kSvT{rb7nb{g>-j`fkciS<Y@f$! z6V#5J2I`MIQj4}8rkcp)m{`j}3h(S~vA65HM=9qEb$Kh{4AsSI3BBf%oiF_Q7B&&i zqHB$8|7}k0aVc?e@z&LM_nf#mx^1YWO=e>yU*f}W&!c2dne)ndHg2b5>sXINPSn2P zy2ygA=YI~F+r|Sf(?T#{n=Jq98%z~X_Q9crq9vfhh62A_0iT*>tikjw`j7HM1{$?p zaLt90Zzuj2vX2Xd?4xY77P60BR(i75QMb?lB5HG*M)HvfvZy^PJ&d_~=OOvn+c2VD zJ&oAU*ZIwAmLGx-6z5)j&qs={u+^YH;lKyTlw2ufom5?AL<8ZkhNu{E95eRol*I&x zH`1`(VEk_66r}YkC=Pwik`)mUDXqO3=XkM7c(%3M9Y!jb=yNZ2xAQZ%N!z!SxPAew znPMXyyzk*PN)~UklQkwi?`O`0S9x&GMN5F`+yZpB<;1L{G`^m@XZK|oX<6&>YQOag z_zsUNTvJkT*i$z_h3Tk0yQ+J#?9wE$4A+Ha7n0702fVoIcI4I%Z|d6cR=`&^#*U(M zDhUz07ZJ^t-ZS6vM+<|AHDUv4*tmR=>8BuPO)a^;TC}jCxV$7Eh(&JB zM%^%;Poff{M_r1Uz(inSEXuV|oVb4Trf`$_1}76pa|q(*iYIyuj(+Lt@)z}I5eoy+ z)MnHETS3xI22XAAIhOu??BB^pyt^#bS9nk9qeM~Zw$@1v0)|qyp9+S(^!d}Sn(V2h z^sHH_B#H5r5^Kg?IYUSS3sP@qsVmcC)5*u}Dcak|2-=A=SMSyB_pVH%94>X#kfiC+ zM5h`*Soq6yp7B*f!oo`rY-bxopVXDsM>R*kKQWBIx%-f(v5`kufX_>Spe?$49xXCw zaC*aU4;tg!{PgM5Os{#jQf|%c(?^j$3N^W{Xq`+A{?V@F=aH&GXiAh%o_t`M_h^-b zo)|8*DiG67D7W7~xh>(`uLO1=9_)1xG6^SJb;C z>Ah1cC$-NQ;dHKey5*{~IHhk17cheeMd;;{qt6 z^9AC#{lp+Sr#fA423Ppy1c%NSCkoa%t(W25*k7Z3)HHlUj5&LU9DLcb4gOqR7FP(=cU*A}}ZJT{IA9`8_oAXE+vr;U^MHh~0Uzh^hC|sO} zY{25lGwVUVUL#MhFRzwE)q301vp(tQK?_n0ANQd2kh!8P6c%;QG75S&J1Q}KOS-sK zxX^8KkPB#5_^#`UaJ+3BSPIk^`(E-3(@O{#=BBmZr<5!mH3}!oPd3GhEB@{N;cW?NrrTM%*gqXHa@?E=eqvc(qfS@{|@)Gp#y4uGI$RZ9Ma_n<^(02f?*J^qgd2bNZ>69!{ z+oo&%FJpO7{mQj%-i1Ufn8?r2Yu25Mejz0YWQB*gQ|HYvdKfrfALNpt#ho4UUqNa` z2AKY4`EiL2il^AMKJwy*5h@tMgK1Y+lcm`E*o zJ>}3MvKsiPi|ideQuN-`YiGy%*tdC(-_gGXhF=L(fiQ2?ZP^7*6eES3$NTG4QN{>P zxCl~K3qxfs+l0@4In>uyTu73EoW?9tZ=n9MIapE)ccxeK0NgCwd%)F)Lrx3ntIrU+ zNi|TyIpfv-yE9=l3!%9Lp;^6ffuJhTAMw?39t|)Un_uvH&gA^xgfc>J4Wa7AZIykw zIab_JAxyxO-;bUt2t@|v_dy&Ss3g3R<}Newt4uV(iLYaF>l@JYOx{!Z3r%G?m0>?N z4Z{&Ka~D zq|CRcqYWM!q-Lo4MU&s%c{yj`_aMWxv@O?5QxfT^2kQ9`UNmWC5R@r~8FV>TVTnK* zK_&b=oIgUFx9)$0HuV?&C!vicf)Pj~kyrg1`Qh~)S!lt=GY_^#KlUq`Z}{#F9@HlF4m2&I&p z)878p-T#?H6FpmCMJF+}?uLc_hU(Fq(cQ`p1R2|L72$rH(q5w3brFdoqn)ss8}JnR z+H5ZU6X^Y@z|a`$k$KXYdlOk_pHWEt=6e^B%n&&@1&TPJ|wp71XIKxz6L7`5q(NeB8Nw+OcBWWN0Pbb6Rly0@=^So5jBdh<1y`-oS zdJ5<%E2_$?DuVag9#K4BL5Y7@@%HPd-!2UT@3=WARGP$Yx>1gyUnBc=m+g{2U2IDx z%ufMKXc2KnldETu3wJb%WglGEyH5_@fuOSWvhMvFkhbASG72EtPk^lc&3PD6veQUg zt7ahBHlpLP@;C`v(s!s3o1^+1=EAOUE0F51rDg>Ye>6H%IYjFL3n+ln=}3AFz32U` zk}ikQ!N0s6Qt0VvK!I+k-}n4f1QNUfk>?u@&x6SMih4&demf%ecbAC$oQ2$_83kCK=|5lyZL7jZTp}s9t1&#;Tuey3Gc?O3x^Tj zjmdr-NsM%HDttgXCamyd!w;^)>`1gWP%-A@0@j*_uG7; zO-8Ov<|hf17*Vnt3_%^8%hKr7g##0FUr3HctzOSPR_o^e(GQKyZAHBe&Fl}WY&VX2 zI-1CPTsO{lYjKg=66zR&rqkHp8S}g| zh9A1iAnbP9v@Mx|f?Tj1@t^meBJG2Q$A)^Enwn;Ci$PIQHq#~GY>*=hq3^>oOso~Z z8t*kf6E>^8&lObcEw;)0{C!>9Ov9}m`)j%0qdxtSXfR?F*(Rb+;*pSOqASuKTDxxD zC(jJ(m)I1?iPCju&bB&z3v|tVz?96L7`tDdY1$NbzRtJxc}xlkR|YH1zsHvXR&Z%kmh7=d<0i3GAn z;f<4VBP_4Wl~em??$|s;0PIrIb6nLRbYSYo{6LOnRy22wXBOC?F|VuY+q$2%`!%H! ziTBW`Bze3sJ#xo{^O0nIPU-+i-OqU}*I!&b$95d;s1!~Nso#zXI!cx)7gL~mHTPOT zjrl!xlCoiaK&0scakCjih}nWOb!~CTIx~x!3D_Q~9nhYVp2+IxiA(}R`n%9XLonR1G@fW8 zj#DlhA_d<)=5IITwH1KwC$k;XH8i(6+2R87LumLAmclnAQTP7-6jb8>*>_VDCS3|(-EG6 zAS->FtXPtk^rIl4fQ0vG^`?rDX`He? zVs+u@j_mVQsdRj4s^JMJLvi;S%`*~+l2GCr<_6Bpq2egIp!D>_%cVz`*~8es0!Klr zl5*Lj5ao$?8(-5Lrs}V-0NNxj6cT@YxRHAIZ+e_}#q_m@S2<#DtL2k$Ceg05CVoe_BIsaP#~C|3 z>PxpfNv-LRa88Ob6>uPHBizyVfi{(~usD z3{eiNP)-fU5j{0^DLS4d%xbO8m{T4&ybma%@8YJYWoIVg&-Q8hcyANWv^l(aQDGiK z^)9)sexe2vtL2U((GY@XVXu}(8X5!mP0Vf}Z56Zm%!WFfP${u4!W@eksVaR8UqTYc zy(RTU6mbnkr@t^w2yNm=yk(1`xe&85(Rxp~+|O>tHGfMxvqvCb!+5AS-}U|C8Zt~x zQBD#{`+(S=0OHEGUZ8nufYT#)_e=lW6&j|^%6$w}G<%UiT3t*asyT~@Zp-dt#>l>G z2S%BpoHGsO=xIEYPAH8pgjMQkYb*kjw*n*W!j}xgd`JbNI|v27;Q08Rs$^)$ z!XXth^OPH=b0@|K2{5h$8xf9ny29*lyew49=-~mMoIc!`Nc7X;)RAz6BG~+;T`V{R z3@q9n7#0+?1IhbZrsL`S6H9Z@Tcm=X!&|X+QwZ}RmoL+Fk(n;!1I@)`pmJSl%S03w}oUL71AuZ43o)xBj5Q=O&qZC+4+#hd@)koo+qMMxRV!osY5W3KHzVvNe_E&JH8Sn&3G0TUyn z$h+XUgRnJO{0UWlGb0dm0Oz`3+~20T^n#HWJA<}jjQH^m%8A@6N_%tH}YtcPhkJ=E*c^)pm%6Vu!T=ui^FyM0>rPzH+ z1(j(3nQ&fxO>FP58TNyfb76|iv?I}g06wp8vK%dNyqIwjJvxm}j&3Z$S^nM&RB<5h z<^9F<8m6L45q;(1^teJ##w>2aC+9%(?jc=(@Iz3o9XK6dmKuW6i8~0r`BiRDXl5b2 zRvvDgonXJE1cbIg^8$6?AVRUS1fFVAfIQ+jE|fFRo-3dZpQb9Gn0X zA4nVcjX0s#U!hD4VcEIjghmDmp#Hx9H3d*SIf|ZRoEN()Iq_pYI_WyDj1#4eUIyT! z`zo=&zM2ZY+7%THbHWih>cm6Lu7Vc0qZy!v%E4N9LzD$Q?MBsAd6+GulcFssYl!hZ znBZa(6ckiRs{J8gl<%Nqo$+U;Q(TG7N?7s@*sm7%YjjU6B34-9+d&to$hgH)VgJv; zI2qJp@yh^5YV?GTgG?A_tSFRGTMKaTlU~mRfbW)4t1%~%jmlOaOT5}^;|nJ z4k0T9gO9#@Av4h|@o-gbMx*|w{k@=U*XXn^msk_&;jpxR^d@Umq%b}3!VwsZEt6&O zA{)#bK({9c|FKk%w;ayw@t3rXRAj=L<+)T!75@&-H@<-x429o*2PbI7`1t6(2z&fah8Xgjz$MLR#MB#NBZ!U=%ZKsh+^#yz>;9U6&uU-Og%Y> zW=S+LTJb-f8vb{5@GkPzt{~}eu}bP<5HmZ`0uv>9zRGgdk7@EYWi>Umsdfba%eUGF3MtDpPA23-mB5<__UGI0SXZnS4{tQi9%%q%5kNeuvBpp@YR zTCYzdfuymV=tN*~Dsi8(E9eT4tTf;=%GW@$Y_ZUSd6v_d^L8s1+eSI&XQ#wUXCcNT z$PHpfM^_fO*!R%o0@YlsI=E|Fl&|yL$z_&3w-D6q)`6Bce6244gt&-Gm%yjEe1i=C za3RWU@uloi_YKIG72rZvJ!TbF7a)c@*;G$sz7jD_?a>$Lv-loh*-?Via7*L&Gz=po z=pX$OSRVB@X%EblT4WlLT}Q1$15+`a9z+JULc z9}y>%kMi8m&S947=7~SER5b~)*n1e&Et1T__fxJIF3i_{n?+0w3Op;1NtEs@KrkKD zbpN+BAxzWnyt*w66Z5N*h%usO;QKYxjl{FOeiy%SN^F7-OloIN!@nc-0qlU0d0(+ohUn1Jx6XB?SP*-m(ZTPVh4kEv;|;mT-JzY!Dn$E2plEboPYk(7ZyNb&==KV9lvuZE(BR_^(dEaoYrozp$9AC1lj%OUMIp_OD#yu7Rf&fq zgW5c&T8CqZrR&5Rnn$t#R|cE0(bR3YfSfx=gvFY-*d{jYZGsX$dL3Sfx=Yx%6^(~x zcqABmsx$iA8w!dJZ;vZoO-n^xK$o9!t}jGt`9J?vh&S<=anJ1sB2_Ni<>FkargSJ* zcHndICFZx>=iWs{%}70*N~a^(%)xLrCTpA1f>#LkSxcb(*HwCkLag_*LWQM|81qVO zTR?|Oo^^>nI)kv0?xEYzz+hw6rv_}q%Eq$!oYaw}(RoKoL|7E())b0FHv28{OC3IIR5&b)#pyr8|neS6ig!m&KrNG>IUWL6SL-NYU zZ~81qtfZm)x}&3{JV9%0=b}6{UA97*)Q$Rm#0%-6ZPEL{as!|1OGh>3csV8F=iSQK zq5aw>T<(=?dP^L|MI_;iDA+sE%a#@0UEL^aR%=IDBAnlE_V(KjxO^gLg8;(63sgLr(cSK#)x zde^OE(=fqdxw^9pQ`1-z@8-~dznlN}cS8=??BumDO-@8p!PRk(cY)ijuzhXC^;W&= zYj37<5=-r)eHL=7n$+NBcf-q8k*pW#Y9}s_Q2c1cs5V4`?Sfs&E7rz`szIs>Ckw z4PbJRJ#TY{Ng=L_rbUmnp+;rn`m-^sBr+&U7zw#MgtU3XweQUd-Ykk{eUx82`g^wBe9C~ zdVmm6g-CCO_)f_G)rc)VN`oaA;mb3m=RF5##`B!(U3SRDi6b&Ndg-qFkH7QJ_;raR z@aAd*Np5sb>2RKo?x{l5G-DeaWXY|vjq8$v@b6Swhvy8~s~iCx^BKxy>${twn_0Q9mBPa z4yLbJnhEn*gx3=ojOP(|R!yf#;p-?#arvQxTjfgILh2s!ucTv0f}ap)jNbYC-CREO z-|yzX-wko?{`2=n(z<$js${Cz35efClT>)-`e~21+s%yv7c5$&lGi0gz$u(urN_T0 z*I6!Z4ZP$QMrxXKj1H#VHC(Q1*)&7|Q0gSoO{k}Vm&S&oE8Xc2MUe-WMWY=W_n|`Or-4&-nO;gTCENFkk>hk%%Vh}Pc z&zaT{FTLTf=8B6HRTy?0_fn%vjiaGk&x@a)$GyPHb`07dc~33qw?mHYaGAT%E!_^$ z<%cQ@MSF{me6TTVzV=2ev()|+?b0<{qlkr;Tkp+b`R8FkQNzKW2LJ?dYQCuRiFkfl z7u!2J!CyESWP&1^#KZ5m7U>Sg1=CcL`r_?U{N%{=5wV{NiglQ&sHaz4Q159!$i$Fz z(Pzqx>6y{v&FnqKEKV`cnQxr-SZc1f>osZ}0@6?#N%wnVD_jkjf1cm(`3btIoXV({ zjVqmls^$nuddjFEIGlBHe2b6VHJ@qm?P(X`Q=Z@_+Dr5}Ks2Zq?OyR}{f#BQd+@^d z82C2tWDp!_ilc~%WZkhvvifeJ#Gh%Cw40L~hV;1EjA8CXBFSqq>*>9l#Ck`GpG%m> z3rL>nZyT;|bvtc<#>wcT9tw+aImA2uD*B@ZAl!DfGIbBxNmH!uK%65jUVckk%B>~7 zbNB|7k^A&?+WX7gGwF@fb^RGB>BQY$`5O#eXF}D2GV2heD^D;lY z2|HiR>4?XK-EpO{6{g=0%#gho$mezoNG=9EAz(BE6~*zuFyXvUy#Hu0#AmAR$IGAHZk~F@&@W?9#|xKQr8^Zi9m;R zr1u&fxj1?}v`+829P3*V=P`C-sU_!wQRWci$n_*CN~IHK08S-ML9pv3L5_0#<3k)i zh&04wC{CVYkY&_ag}azr|8H`)^HD$5%5prO0G{DS(GkMu3Oe6P7S zfH6#t^tWO8_+4Rg$~LXc(7rfSX6EDV$!E$9ag=mlvKL{{YQE`IWaBjhZC+lKd+qVj zE=|eNeU0}P%S9i7zsO$RmP=+PZxB?@zcF?mIC*?aBD7b?PAk zCzuhj7Qx}s&}APkn~MrN+I($^uNNI|$C|yW#Ctl4tFYlxbHEbcD|mR8FJotSQU7vs zannCJJe7nCZDIrYyjXW%@5pLVQ&jTa%8hS-;u(fgAEl5UKOt?OQY?Wd~{cQ+2!In*W0 zS*Xc{*?0TU(v__E!F?!4&kYJ_9`pO37gnSh7m8x74`(r^3HG77+&b3GbQaq_o5u=K zws48f1F<{lEzHBi@dW#)62nd0@DavjTg*cFbw-P0oR1MB)o7@w%{Ra_D_ifdzyKEX z%G_&|0WK=x0nLX4T4s2D1%Kwf_IX-g|~cnPy$1ErJ1KE0`6` zfCM8T8FNAq6eJ@VNumS+0g=XpfQo=3p;|#ik_5>~6cm*pNREo+oP{Esb%W6T&P;!2 zrl)+*b$!P_bJf^d^*ndjd+oKudeM%ag8)pxl+D@qQgP{h0kg9T&(*|#--mf%hta^m zv-63X_;MF(_%yJz-iI{k5=3OzCWhq8P|Nj9boyy7ss+b8Z`o zUl!Yo9hqk`$iHL4it3!3wQl?^j+tPH*KwZYUTN1d*WLB^O(ExWDu3TcVFsk3JkW?0 z6q-MG*;dvzj1E+^O%%C=HsIJ{qq1ndVEqLGjg($Y1W@0c$8`=c?g>$czm^Hrcz2S_ zJVKKjL-sfZHmi0kT<0c$op+z+#Um$05rOd<^1g&!(}(d=tTpiLpRl({!pR1@#enkn zF1spjnnDQ0Bc#K{6yP|9bLmQV88!U@ak&^&0Uv#{+CpidgCg!=#ia&%un97at)t2@ z2Bor26_`e|dzxn9cKh8I>iwf~1cHl4QpQF)0x#t34%CV-p_Rd-3}gT9k!*?-Gv4u8 zzMk14);v9InorS*zJ+gLA+}dnWhIGBY+tcXbx}ABL(vt3lvDPwS3L992=N?kw50k3JZ6=UMyU++NSqo z6D*4!o3E?j!5TQJie}{<=W5b=Vc!D*R8lT5%F>8JzARRNH{B`0hJ9tgw#xkSXd%vt zt{YD3!y3b*m&Kef5BN_%!7zDF&rh6FiLNhBJjbg?`(v#`?MQk6<@9>!n0N0BI{R5p z+3Zw8XZjrLdv>pXJ?M`aAbc6yAxvS4f}DZtP+DmMEU$>+1ra6^v*6Sx+ zt+D@f!y(>BU4|SPF=mXb&c9u&htV3zw%C({ftatc(p)7_YxAWT42Uvq4hrmN)51hk zJ+`+8ThEpivBx-QiuKz;xJN4qBi!XSdvJYbekX5~Xm*MYwFkIZSl&-;u-rSbG4SJb~67$$T3gKXwt|z#Q1< zP5qd*;FmH~7=E>Nv+izsYH+g)TPN&?F18plQz*euPW~XgxjKe|=Th8iiXm$&8qR z7H6F{h!%k%CV|htEzTzaVS|MTjS&Ar)=qIa67Sh`^f$@w>YnHV;+Hal(>yzdVHbMHxKg69vN#OSh{(|Rh!1&uRYm2 zfA@wqOcvqX!5r84$SEI19r}@ef)+-Q`Bu=~PiC`XdVY9mZTcLZV-Nz;YD~HEW_A*= z9I-?lwuda{MG%4Z=CTiVyPtAzVxd)(V?dzj8;4))wXv;w2XYl~h)u7;-0Jk6V5TqI zSR-7@ZAtZzpV&yP_HSc#OD5iPCVhKct;yeSS z2SxIwji zA!a@nrNg+9W))tww9BKMv74sjJZ#s!%S1ScMK}j{cdm=tnY4L_{%O(tKHt`vUK{D9 zk9_C2bjLZafD)%PTmetWAKt|U%?i9iM{SLgv_hg@QF;)T_#t|y96egK+|JLwwPF_s z7Y6X+7~9qzImSyX+awPk<7P(PAjvuh&mXDlwgJzdXN>2!Es#HSSCuN_b}Xk z-c3g4kKdjuWf;1qVY+<6OyBU+5s!ptm%P|Wr{ia1o>1?luMk%m3boT$8!*UME2y9LTsNiKWk30+7-=`piY_H&_J}Q|+E` zV?qv#Ck&ptC_Sbkd#-_h?F9!GfR^+p7y%l|a)OgRI8AjpL)9O|ZuEv^i~giddPiKA z194eb2ED}`u@hTR22r$ef;dQMET^8>S6i-lr&4iaC-e%<7Ko^#YuE}6*ZH8L?VcbK zH5#&nU4%~_GQ|zUIv+YsaEgFhZK{sag@vIgw)wMzmaf@or`&CM?f;io@r8bI@1J7O(uZc*k=1 zf7Z>o_}&Q{<>*ET;PiWV>U&iFGJZS}a`cW^9m6I`UvH!MIuEY-n51xMY_wy{cgDOG zOlPA|ciVf=uJ<5trG#QX475x>WPx)u@eLd)!dl66a}8vC*TYylJ>@zYF9QvM>1s42o3#u z2)k{<4C@`xaU6h3sp8+p9WZe?+UW{MX|(VYZxO@ZncQK+;{(aKWZMfED;C(3Bk0OH zg{SR9k!=uIg;!}*oN%Z%iLoP2*E3_2*@%S>OheaW!L*sHqWdr7v_+<% z&>pb*JM=KVFZ;I_9<*HlSq3s{m;iN{C*_-Zcy%kri8NunKkK2XvOh^#$1%0>aVYRX^pdNxGdGW zY}K?vrQ^23K7j#a9KuWyG8pzyF)LxmYU|brvp2nKCl_AK40k=aK;IqwfcJM+t>y4h zS+HswZ}U0~DmTNcKV~W|5MIkGZnj8=%?yEkRxYph#NxOv4!km+@v#?PKmMEt!^xdq5Y$_8M zAY&k?^ZKvOJVJ-3&0aWbaa_}rhxtu`epTy7F+jNhqjnfeEzouuw54jMol#O(rfHd8 zAamq3R$9J<7=_P1Y&RbU@`VltBb{eH)`J#){n1MpkP)QM?aK1Ul8qtr*2IZrqu73D zl{%_pc#xZoQ8zetSuB-4Ww?%qP!I#B1tBQ8X6$^-$9V~(0c{AxtvDZoj!_T1 z8uX><_5H-v`LKF;lTE7z%TY`#>4*&})$&x%$55=Qkz6P~9ft}~rbjALm~Hul7ViD% zY8%}yd}-=}=f&fG`;;rXqeOW9ghRZq!n|VFqb|oyY|Ez6asA5QNO8zVyjH0ZP6BUZ zn-)bq2*J#&i@+7{%{I2(K_c)s3DXw{Z58hOBr`9gXWQfjq{+MLo~%i%9x)mkDo$02 zYRx~&)8-po&{wc)b3^2N?%+Xc$E60rCgah1;}wCYIaAs4)=EBZ==_*ereeBx^R5>pGJz?HqFSIhUs1vi5n3pB zw(Zo{(%^%XxV`A0o9N-Oe&aD&nL*5HD%dR2b)#LBTE}Dez#Mf4Ua4cXqNBCF;qZyf zpQN`>E@#?IB-(utrq0^AX=1N3t@1p=aST277#z7QwwCVAstd;wkYA5k(E{@0T$zTe zaDrXpsFp#oWO3?in>u&71Uoxwd9{&Q8{s>_8z>+0z4GswgAMt)xs$a3|0Zy;H=I5E z?TzSh)!{Eo^+g`=o2V-wlFs%+(uo~e-njZ)+up}40jWd5sdQ6}qSP5!3r0TGCi+>u z-`(Yi(ZQA_NXna)mft{izkP660*5lfDaWfAooAj|J&|uVh7yflzswu0cD$m3mu{~QVE(I(`&^2Ku1`=0? zDlYr@tw_swGadaSddxJr&z;b_h!|fxiUT~R;L`_3(9;s|opjZk zLyeW+S4;K0aS9QQL=CA;68-jaK|aEke6%pFJYo#y6gis1O-EISq|44+TfIV|MT3@` z!J2m~4U=~Bt4AuU3xB&0Nfxi|T1UdqEag0X8*{o8dMs22-^oD)xyS49D?(+ApJ@AI z*pD5C-qEuc>LyM&((T%5-jE z*dRtZNjJmC^V#aVNN`SW!hik2erz$gGr|1Xo4uuKyE1rew~f3K9p0;az;2}2&WG5- z!b{Xi*4iv|V2YAQmqnGE<$z>A>JwBa5L8=-zTa{bo90T`;IbFtX>}?^vS^5~DH;r; zy^>=)g)tUzS09&R45#yI!I&^FBKm>JhjKk__qS=}lR~tFBI;CfkCo6(1}PLhQsy`2WwJGo4{P_lLO%g|9310)6s{ zrHTQJyxLDif4iS~m1RROxRSz|3Q`e)~u>04E0C&cSE2$GbdgmAWP4sgGjK>=*XlJF^lxp1khBXzSKQB?SRORB@ zHbmVcWi4P}j}gW=y9}sIc;w5{)J8cWr>Wn4$Au!V_R+T}K)N(pT`@LZT_Fu)b=ZDE z)oN(*8f|}6M?(;Vc*n#ZQ?ddfj9JJjRDWM1U)iFv@Z$4)5ncM?WR7tqqPF4TRI1{C6f z$t9n?4DGtz{QO0!QKI@B?skKoc868Jr7u0JeyRO}-_Zray;3qJTvJ3GnJB&?v-liGh^Amphj@s_Yep#$xl99czXgoEuj#f`L`8{IwbgD!7p7{_hQ{b!oGwB!S;hW&WVj4}knAT^x%CPeJoiv2O{lRqT3 z4VNW@|K=Xh?gMkarxkZh}R7lHg^g+$1VfM@VDKj?3UV}b8`kg)v z@~lB=nd+5D|H&<-*|Ey|oz8EhRi^-Hi?X6-k)8*+{s-4s`iJf=nPR>vcG$3y3sIz~ z&gOe|pQmrpu+6^Oqf#DB|6}|A^Ar8={BwoxEdoo>&u$24g?@ILWkmmaCBJ@8nas@* zBNe2IH>dXK$9-VrQ8%gsmCylK8o;e|`G@?yCRX)*p9z z$G`dKZKeI8oia+}AATC)zkB)r=Q{qCoBY4n2sehOk&Ir5d{lxbl(p8IjYLgxaMS4e zkQgu8QitgK8QTO0Gxxak4{}1xOy6o+L$e?9$w`T4P3^=8+Adndz0K&+yf_)Fc>kbS zg=14^-rfZPnAx86q3-z*_9lY;(cb{>q~p$9aFF5O(-!3g3Fcs>lvjdiwGUwsxo81}vfV9K8&Il6JB;YV zgM~o~+wuGDus;Nf^pJAI?1hc=lO==OCrSpvInAE7poP}_XbkDctqyST;9D{lSzX3R-?KwqEzunZ5Ao4EsG_mFEcXIPe87bmno0G(;M2{w)5}7Qf^^o^t6l_#m}&+TiZ} zk7Kt^I_Y)v-kZ0KJ88;gI>)z(OH5VLu9_8na@4(~)0QoS(wQfh9nAFlGs0+x6aW^r z@EBDKr?`6;?1%Zj+$e-I4q|J&!x(%#Up|z%p6MxG8iNjyne?kd1TQz7nEd;+z>aKQ zmxsL{eN>!G{z?5xm*d9#@xXr6uZn%&4YgMS`ptWGrbP8?uc4zqB9rzqVJB5xjt2WM z6=3H@ULIa${6`Y%AofG>QG+}}OL59V%i=3J?`yKz_tn0q7$^d=OsAV)v^uSajTS2~ z83r&(h}jB$|^|^v%jYK@rB%1@O31Z<$myrKejv${GjcWQ>@EgoFc~U^kD`I}d4EO(FY> zdmep|q8;?Lyg$zJD*nbs9w|AwrBD~i?w40od`MO5UK#s?4=vlAX|PNmH$D7cxsD3h z)uX{I=6fe2+{$Xlp|&L;5&z;y)9#h+ zBNr|-Ras&fdHBcw7QE#~*qG>{Gkib%y|8re_K4+K%138yqG6%pU+(EWkIR&v{N`(w zv^)uab=ZDhG@6{BpBL?~yx4!Xygx4*O$@cvPme|mdw z0THXZTnMtMp(RI7dtdl3ztGWhoB%h!DDl}Q8eaU9Daa7K>xf{SJVB03=X^Q+ z!{YJ(^P(?PE*Q_iMsP^sm+2lp+d#`-w#P4X=teG@{AC96%dBBR6qMqXd+Ba&K6ovYISTm0|`X80Eb)6*4*2u~H9B}zdO z1Rn>LpQRxvo?P!qe2&*LA+PnO@)=%>?I~9NO1IZk2+Ez-ZU#^T6H#YLaN?qy?UeEFK_ygIo ze>(3{^;pICN%k@SE0q^Nw~Pko=jWEughTmx%l^(X?p*&Io~8AnmzP%^?=!oYOqbAUR z&)l;DKf;Jd%c)@!g%gG|X}S(Heq2w~Do+Rt(bwV;k>Izw;-x* zb+qosM;L*10GdB)57@gM$^s?*){G4GDQX{MRwGzF zc-;ST_fX5z$1XRaDVi+7NCpD7VkCWhgrD80rT-wKnS`&vYA>&Y~>fo9&PzlG-rDUG)GwA)JrQcQ}ZU73C`NW zq@RyDLT@nL_)Bkrq-#P#!m=IO$0B0XmH+g5M-GATp1tdp><@qfPmSy|#TyRS-Z{AD z28KGOp1L14S0ELtjT#tcTI!1_oW)_39;W*kh9bxJR3Ag-T~5i$Z#1toD<*;*!Mi;3 zFCSSTAo)AhqRLX|0C!{3y(%H=N!A{aMhQnVqc2uqf>QoFm*p?v1pRqB2?if>CNVZ? zF(grrJ4q`qH?_s|z09|F)<=!(Txx2IF+!PNGMu8eVW!lX;l_iaXP=MR^p|cXf&`W2 z+7AuCU}BH+NxOIz_cf*>7&mkuYL-Q7#2e~{Jj(c{p4vVKTyWLk6YU?6Y`ZL$Nbkic zAh~$8<<~n9bg+o#*9l_qiJg%bYg~PQ_XTas+kn|JT2F0KdUrf>!IdE&)2@m1mYE))M8u?{U z_9o(x<{ihTxBb>b+tH)n=@J1&R?$Wb9bMoGMGV2%eFN>48kiIpf#FNM@vYtTK6a2W z=)!-0g9htbjAOo#1YyL8j|Wy}n3o$a#qesgxvK?s7Afe5Y85?z-bfPE5w>B%T&tgH zAI=h)1Zq>I?!*cEwkH}J`8hAuGd921_2K7)ExHm`ocnrhhF zI1I9`g|d$;(GgA=Y7Q>Ji$&R)W5#g_WOg3xb>La z?KflZago8V%OH&tm5af|oSTk&-?vefk#Sqe2lQp;uLVz%0!EL-!M!J+zq({Uz?oN>QApiv&%W2PWzKDzxM)B z=s#R1THR`?!*tFLIWh~){X}fkbG&!QZq~L^kQSYgJkasgD>x}^-G2)r z1aC4|8ds=YlB4%g1-RITiIlu;1+0HMZXA;EfOUN*_y1|7{@N}4XNU7%kXS9HXYq0V zwJKIk0VS}%7nsdDs)VaBh4@?5KuR&?UD1`ChFm`a!CWv8z1~+4^1)vkM8_-*APqBq zZfadc*ANXr2TRpUT^Wt#vBu^d9d={Z{kik_Di(Swo6DOUKQxTFfst7osNVgmEQeHZ zULWc-e+v4TYB%<+`#n#TS^h5FJj%tE(AdaazyxA2bo_9|`@R7hM0IuzwZQtge13I} zue6X%dd}gmz15nyM%d~#2K+u!j5RW}ot|GYxN8hE60Z}17v54$x6@}Fnb$dn1fCm= zK9Hi^EFP|se7RqjZ(X$-*H)WfC$68&MI)-){!#-&`jFAvh7rdzdQ1=GSvJcxmjg#E&lMfJ z#A|28<8~r>@N95sk+$*l$vh+W?Pv0YzNf1HcdFh208S}$Z=kNY{ zI}YkuD^8Xvn%G!O1+!QHqGo&}&TVh*V_fANf}6YP|IgY>V%lA|oT`-ZZE=Ex%Cz zAUSLNidAtT zVI`KczrN%!k5~7(bLwbcI5Ez3mt<yQ0Fv44`m@+P*&vgq~sr1Hp#0Le_HK-uuJduQsOB+;VDBL(lr-tC>GD^N2wjI4)X=rz|I#F zMCrZsyWQA;ohyXGcUiQ+65k|{Y`_8*KoWk+1uCKyUJxK~cIK;!6JM`tS?B&CV3bJD zJhJGhGBkoXO6qFjHvQuZ`fzvn?uU2n%kJCktiQ_C7Uuj7)R_vus&tR!U;b3g6j$|0 zC`u>SB{a4wA(o!pKKu=AyCxsoG_xvr=6LtmWNSAh<=30x#!Nsqja4>R;BEzQGC z-VSv4;t-|W03>*Gb)`{Bs|LhPz#gRyP{7H6%CsWq#$I=x){6zInUGV+t zN1j&zvKFrk^ge$itQD~q-^z4l(Z)BTqbB4wmin=O70NzgXSP4jQlaI(hRcoFi#KV$ zH%b|-Cs>d7=@}ohqAbUVYRDsR!?5%DvHdU_E*8}`d~C{pB}n7jxyf39ITA#UYrSJkp#FQV*jv!!()5GQ%olz* z6$Nh{=<{q-OS@cgpXvFi1IsWgRWZ-vyQQylQUsr{9QV;4vS8UDReMX*rWAn9{$D9N7|jZ>24i z=HHMR+e-%A_LTBfnif>t2aq@jqaB%wNGZsD25GDobDT{UEWIp<%mWbvh*-WeYssIS z(SMhMow9`|9n#=%7nAs3su|^yE9jNw6855_G-y;lN_#)>)_EqtKUc)ao26%c!!mj% zj)4w1=*6S_o$+tI0N1?EQ6tuzXu6w>^cCy80<4d{*dE_--qrL}-XK(X)gL?a-UPy8 zRit7l?gxe#7L(V%-no)iVD|0vwKrr2vBF^qTTzV4CK?JPEaDOoC7=Sbuje(euA*(x z1z#`(_N++?5z0Z_5@B%6CT|d$1?q@(a)m7r;b;DSZB5x1Ari`vpeKGCM5u3&kOnv0 zxOmIwwd97gX`oDMyOD3`Vpgdb!hXk?@uDJcDqyC~2TCM8z083n$5J4J7!pzY#?=)- z?f0xr4KqChr$K1hNyBCLlTu0#(IrMadMDfc%Ez%TSG(w;ZW81X882r+3~H~1CHpXb z81-H{aVN0l2oF>kv;jNqW9%OVj8}&p{Lr?U;dVzrres}Al~v1Wy#RN0HwoS>hDU*{JxsbPc0}ju1eyXzEFry=A;DMq>5Fam z-nWpKQGN6D#N)`W!45G-uka4V3vSup%{z6-)oxg0$vNM!;ax;pnjd1|UX=w_Eul*x zYWp@N72t~yG4nF(Qx`OitlXjf;k>o=#40l>pb$7Oy&g(^X_44~Wab~>r|$iBS?yiS zOdmTO4*57UTd@HQkX`~jV;ts5*svvDK4G}Y0{w`PDlCXs33*YZz}24&H{FFn=vo?t zCyV=hb>;cm#O;m4nCa~VpUxlK4|GiBY#pXJ%zxJ&(Afhyvi-3}l^#@K!q|LK9qUtt z&ac=2W~+yaU$+|dA6)yDxU*ZUc5fdrv&|d4y>_oUv^BWeH`|559BH5FYbt|gh&tv8 zmo{J%1-awD-d)mQiJ0$8jl+T*XGd0>b-$$spS`r(dYotJG{L0v+iz; zKrC0Eve-1GBK+=5;+QD==#P9BiU(8ceLzAY`+Sz^*U~zLr_K&70|g4Jn~r(q7&N^x z%`Y{;Wp%|cXdzh@%R1h$lPg{w$xhvQ@}1pMLmSuksrg?{!3$pOm!0hTo-!N%{RdV_ z3ey#Kr^?&Ioh^_ddIJT8yjBl9hC8v<4@X2mm0%jVGfv}9zJ9>e8<4(<$-dwUbbnl^ z>vhF9(0!6l?HFRc4Y9Mq$ce`XG7hrXFfHD6?Ct1iVYn^xQf1Q&!4O`Rlb;8z6 zvsd@q>?$(U_$_yAIM@4iPp&8nu8aOdO@ii5gL*!%5qRvuqUkf|8xRWm9vubIYF4XK zWIE%Po%gjN5x32<`)g|zIEH1954x&oKj^L9-BfDop?7Hn=EC4yif+f6#Aby5*4$u1 zJQcY`dy|6u^O##W6{6XwCw!(&pJA#S?@{G5U!o#wL2R9H``3-Z=LL|2FE!r}6F6@Y zjf8wcP4*$X#J`Vh{)G`0W1qt$ct4WKz+-cxP+P1?oVo41LGf~+5{cgSO2PUZpNI3B zARwHCn;C&2-@NkXwybI^Isw2BGFFDAN_P$-zV{x9hss7K%V_5{t@@y#psdQHYwN~2 z>nGodz1AucuD)ThyA;J0hqd?u8Z9?Pl6vR`mr~qTsx?T1h>RtQ(ZM#NBTbToG|uuJ zfp!*CcyhmjTaW)Qy~L|X*XWKE@^?m=@JK(s$hg$_Qh?N3ri)&5n+gEC!$YpR%c<<9 zQep3Iu6GsMK2QQGi)Hl zpL8*@$0c{gKUT{yO@9r=NBxDP$~VLt*yBWemhx3yti3N{W0hE_U~N$*Y%*<)L7Oml z+TByT11LSu3=$uWPhA-vyTDVZcw?sj*~Z5!Xb3z|^^r0E2`58K-8_LY5b^&G5&V9w8iI%fpw#i_Y`(PAc8<;lq zDR(#+druV3+wnvOB?tvsaz&^H@{XP{eLh>qC7a$UQc4=KNM2)yPiPn-df78@K0*wq zjB%9a<*@aMCP>>wy|~Y_guAz-HslEY#2;Hc?}iRKJ z2@RT)jnh3=OWXN7VxsoGegAhW=e3Ncd^E;gJS~kK{83{JMIlW@0kE-d% z{aqxu(8!N(fV($(KGIvuo;p+*etrP=)VUGpTPG&!Xv4E!WV2#K&wqXUA$q-dgQ2BG zLbORJ=O&1CC#=Q)c!v_X0d8it{+9_yonFv1gK_c}s_y&2Pc5Yc-EPAxfx#)@AN=Z8 z3o6N|FS!&!X083qHP=I9%FaHBAZTisP^!2UJHK%=s0QB7F*P&K8^l_wgHqIcUWFv$ zBCx6E!7rkB)_A54YbhaQ3eAbY#%hb#61>FATj-UZF4K9jb`6m+z0+TQQp{>(4>S;6 zz@Z2iA!gWAYzwNf_U(FC0~42_nKEgEmJL2jD-+BVwZo2b$8X4i*z%H=qh|)7XMaY? ztngH69G+bj7k1Nhg~>Qup>M~8-!gR+i^z4$MqaMDa0}rIPaD1{^wMH`LFp+Pa^I69 zQ(~DQu*kczOhCCW83lZY5mP*rdEvasRK;+$9F)XPt(9tZFgwM%g{D&>1e7s@=rw?~ z=bQzpkQn*`!6|*Wo?=zI)$G^7z?H^#v1?kB6n6I@89_TI)fPmo$6yK&BO#wL04z+ak_>Bo6M_;P;7M^ky$vZkwdWcFI5hqq`LVd2T@FJUi;(x%~?7k5z5(Bffogy9236 zLkO({BPUdMHEGAK+N1k{$5a4Udz2i&0(H;;VC)8o~ZXbIss*GF)dPI`8M);vE*y0 z(I*PYMDz1HW+0>^0iX-hPA|G_xBUlO4qph^`b~3$XRMp&L07j5`_~bymn+Dd*C2rI z>^4RsfMg)Ki%*hkyX45|K@GENt>slpdmml<)c=2~=+vat^ywdVD%wW7|AO4!uva zpB@#pr?v&K`o^_2R&4MBD}hTi+FR!EByE*d&ft2sK{ESB$A(_|n8qko zwUFG8<}ePfc7N8!k*5*@QAF5Vel>2*IX4|oVyQ=5g}M*<3luCri-`Hds{94tT0vME zmZ=CNK4EDfm@aC>U-*e-e*D&vsA^m4E^Ch?FOq=dO9d1SBK9M4k1pL+Fsh1tOe+<} zkYmPzHQqL1siMfz9vbuC;uBQh+7zu-HN@cAP?=S&dC*E)RI>Sul`)Diyhdt{3tTgb z%-SUHgXim%8cGb>GScA~=xfD6eHIqRjQ}y~!}I7jh#!Jqj>sI}y>UEj>nowm5^t!Y z1GjOKA{Mcri1`prTdo#1(xI(T`La^xQ6Uf#@>{hMr1@)+xuG}kG!ZoQ(B(#gG+@@sb)ONDE#B;7D(%4XJRaQJ`nKTs zV8DaY1u6c)L6;lHvk8DUFUk^L%@^cshX*iF^6Eie;fCE%pxxR2`1jn2oH-)YR&&3e z`~_c#`-tDIP*&q2*uIS9K@9*6=Uv@3UjT|sZ))Mqvq#<@uifp|#w@4w(O_9WIH}0X zJ*Wdp7B8`>wIqbq{jF#AxV`Mv@iCKCvK_1|T_0q;c=K_Cm}sUCHmDlWss^w0IliWy zX@#g!MVz*uzG2nSOzh*~T+;)R`9e*pJbSOsy+P8583XM=wywyniI8gCTl!p$!$6BS zAw0Q$j#PLmH`0>)0~Hq(ZF1**8v|Kyy%Hu%%APJm2pL2`N9 z=?!NKjFnf9Em-N8)vWf?~BInY?^`^*Q2fk*PL8 zX;rM2m%#_{(2-&t!@=GTIf9Qj2OSh_eZJR8ylYT}opbE&QEol866w6bD-3fY$DHr5 zocFy_chF^3X?@D_Z)2$WwqE=<*7Vz0Nn%eYPvEd!4bROJ_qY^`BKO@n;h}zehQ#{F z{TlDKyB*kcR9(}vb%%Q9UGeq%*6%n}wB&%MlePgn`y`_oIP$_=B+epKqHfz1$S zVW5?F%aBq}N(a^Zf?p4RV2eP#YlFnU2$cVse@v5Jc-t?E>9*%%xe62(oB%^i@~I>c z>`I@*^Y!rfrr;8f0A}7+mC_B&~v(7hP9BK+AnG^N{k^JHf zh#fAHn{1iLbjxSYOT}p2Uz=WSZA$tC%22%{0iNi~tsOaU%K+;5#&g7QHmj5qOtYw# zKo;tN8gH+puH-ZFuH@OmYK2mgZP3qc9fYg@Vr)lM?5C1omvG)xWtg=RO*aYk0tvqC z>CXt65(iiHzJd-c&2<<8VLM*18;4#tI4N$D@Vb|s`qaOsnaSK|&s#Y~!xyltJfsNC zDJn-`iwbUdl8S)7c}o#jDmPE#Fm70GND=bx-%u*E7=ZG;|F)&>kyj$#+*bQl6Mado+XwaoVi z5W47GP1Pa$r+_k^= z&-)4t>t%&}%ol8b4zu*KUe(cV&*6kelpd0&_O+~k5Ms-iYYRm8NF~`y>sbmS^@x_R z#QI#4j3s(|w#hrMjMs$1BPx&P0Bmdl=94u@0^Bj&dG-_A0v5@awM|KK52Tk}C4Mmj z$Y0i|hl#^CJwv7mQ$PHrS|3S+GABg~bJqyPeMmVn#C%mVwKILr#~8!%BxQvjl0U0+ zVRtkQeBS=(ZofFAq@F;G1@=Pc#Eg>^oFh%SmkHE%1+0B7PS zw`I9$RiZ`5p)sGXGX3y6vvx#8M7?rLRNE&mOctr|0Qyg3WP2X6plAzg5IZdg75~X7 z4}_C4){_%AT1}6Uv!&1E)KToY`!Eh^ zgZD`eWC~$o*37a+X+S0@&ZI+Sd*|=oKx+63ceg%&^#!*qw}rQ;v1qT@P4uJH z^~sqXwgSEK5#=D~-*iX$jc{hVviJ#;XucLpnQgmZ75~;bRmV3nM=LK z4=V4iNR-M`>vlB?IVW!_@;~UhCL_Jx75>Fwp3Lnbl*+`MruuJf6ms+y1+2Mq%jq4G ztdI1I!<-5|kJMq#N~|E9@pC#Xh(jOap|eM5XY%m1Oh7oC44UDSInj_bV&meUQhHvK z#4LZ!<>}qbB`^de@A8o%?y!G`Pws;qo9AS6a}Wlu6-fjqr7S`j5Gm}C9H7CaNF8Ri z@Ptx&!1?Wv<~KX>V3)q^PNNF`RFUSVkG~+~dLK6F(1Mc%3Cz3*rmL1DEuaaUNeVp( zxqNGlaKhkWYWc25e&rkJqSuLn-mvs>yma8~! zYhzN#5f+=?69!3LnT1srTdZeZ$&u?{tu_kFbbH+8?0Mw%H;2F|SCzk;tOfYjPK(oo zxF7G5Igo&sc$E=>%T7BsVkK-pH!p4W7ysiZy>~2gxYwyNNy@eP75ch&Ldcc>U_UmG!kr2vWb0vW>8Y?1xLqzaMSAvDx=Jsyv>*qwZjE z9~;cI%h{W`rCIj`X3jW#e&5^zUX5T}9_xE_Eu!CQ99k_}ib6HOl$Lh*3R`CqdG?!E z7p*jmKJmduML=ZEciCgSbZ4F-JF=|Xo`w%ZJ7sbN#=pdtYq*fAaS!qZ5FD=NcY(<#}GvfZx-u7Xb zB|^A`Y^_N;6)gL}@&x4@3>=h-*BGPpWZj#)d&;T3O>Twka|Omqb8VL(^PSsH7&Rjh z4U7%ZDA0cck?bfdQq|tuQ7uYUen9CV!K@LIts#}$-mF@TA{C2>0hJ5cyG-gI=x7g+ z94#rDb*$wGPXE&CHd*_`Q*WID|mYq&X4(dtC1CllT23gN%2 zwp$dD(hV$wv|kxF1vx9M)K4aPbuuQxp(Ws)Um=uk(Y=Z7lyE1@#i zhe;M&=;9?@6~~u#b@MnJuLgE@o}3R`NxCEYsf~|aNyVm4YNBb=-7)TPzi4j4jF0j= zx(Av(kzQtM3yLTMRFZ1gN91l7BJ|Dj&#{R%Agk0pQ0TY~M)gJ~`UNopGtAlS{RB{8BmY+r>`POZA0Z zD*lM2QI zUBHz4qqMW%wLaVWTM{lSahj+qWNs^u%?(dQl=KvZvGIEay5Fv^^&1=3=^y^;Ch0V;E`=C_ z2g<7FUNV^A0c0C{abR8pp{>-D42a}($k9J5akm_M*>Dk&b?tb!)%*8zd`Yd$W4QVF z&%kAmOlmVr&CcXmGVYTU@7-TVy18%|cv9D(|IFX+)sZ^59J#7vbFf&StZ;6hhxLig z_*3!cKNAW6>7TriM#dpjY01>s08YN~m=SjRw;>!tkCji9w>Ksszd&4Mz5a=1*OkxP zOLv-Ar`YtAf48~y#i&h9eGDHl*f%`b^OzK0Cdi)@GksPPu9_N$RDUEd`_>^?b}kD) z6z;DlLEf+>i$5>5jYJ+M(m#>^yZqvzM$vRGpl zGSNKz=nNkg(>(Dm#CSF3RtU3-swx3y3{zkMgsMOyl z)fb$%6WJ(92sO7EmPIGE6GjrF1GQ~^_N2NgXGt&H;OFbkY^t*Csc(9H@Ct6#k5xfI zyHKDar(UdVksUZhn0F+-PCwDeI@iavFBKUU9kZn`TxBj>Da5WDLnX;Rh4+tc_i`A% zbC145>2WvxEb;IcduBy7d6p141LdeG`SMBN`u8r-_`kB>xRF&<=WteYEC(IprVkH8 zBj0hHYT5(gdvYDs*oimN)N;sKoJ+!N_TLgT*_WI8?s+h0`hC7~gD7%$ennB;!H6g9 z{KFvAm2Hh*&Exx!9xHSG!ifjlwT=+7e9(;X%W?Y($AC>}$4F)Ohk5@PP`?|+x2G*C z&4Qz405Ozf|!bR-KEH^fxB0&TnjPvBLY z*p<2RF)FX{{R*jY-A;WrNZ!3hy~TrS!M;y2>zzMu79BinYYe${lJy~V{XNz}e(5#+ zxGxz1;}1oRQRc8DM+MwNYZZc|g-D!O2T{I(3o8)9bKV(%-;{Z7rFC?_b>Net4L%vM zsB#}~QhOck>IZ(O1gwtJyxnbnaIS(2a(I?6QT-^gX1nGg?{n$1E&Y51Epq7E-QQuD zUAVVr8Szit#2{M5i)}wdOaJuL3BKV?DKGQ(;dr9fdbc4F;ZVrQxQmQ?NZdiLWf5wWp0cqfsHWPQAp4}H zv71CcxVFk)`>E9Gu{9)BeY3Es?1lc^mAoPE%6T1?NU_45xlTtI4J6MLva<=w``5JL zS3Ru`dUX(6(CEOcg=F#pc7DOW*c^ct%@jRXX7^7An-fWXU`VnSeP4#^NNo;|z?pK} z4!&2-1fQ`cJayD+7&%N%-uCDNdLfQFIXkbR-xj36(iTx?9aKWzy0iY^l9tC$r2zs- zDhJ*#=(MQba8t7T?EWy_4u6}|GWycny@ltAIOcW$dCBWuV(gSOV=T5qrPZC$)l1xO z_M2=!erC{d?J!QXoGDQ6JL-8Y1GdIXT1sXYHi@Y@EDwZKOdO?na7vafqb zkcfW-ulW8|p7Wh?gCFu~+-SQ4)Tn7W8hD~}MkJ*hw|pBZA!RJy?Ra{@^6eYuiw<{g zZ+Vi}VmgCUo)jD)NwD8}rcdB6t)zwFG+Q4qj7M^yKpl|3D^tJ9iNh@Oj~JnJ(n_Td z59-QmSf*L*sDC>>8!73LLoBN~!u_F$X!N!h$Xe7l$9lciU~0~7T8E{zU0FuO!N-!pM< zlY8#T9KS&_`VpC)>Aj@3PTgz^_&|4=(<3O{^oc#X$!)WwOQ!Qmo+9g0Hy4^35um5W zz%;&Xw?Ezc&)~`b*(R&8k8j&o5~7~DdQ_nC66krHY>H+2?9G~=zXX}KmF>bmj46rYT;51*REgc#$a{k^1aX8^TcVeu2v0c+AQetiQpjl8NwGaz6#+kxu+zcnDLef_9<&)s(vr@r>9X`S`hhNqL zXg<5+nziBD*E>o_JP7G4*y3x|93+4H+kusa_paHH<}Z_v`6247J4LvtfG1KPMYzx2E{fVpKSoGlQV!sBG%~*! zRj~QS(|!TnaebWbSm))nn5Jme5){XnPSQJ$;IB)u-^1vcyg^80Yd=cQST1+trK~aQ zff?ie;Q0o6*_fWM1J!mc=&m|Wa5wDHV)jpoDB>mgoQ(PrV0BXY8v*pQ9~`s>x;Ern zW86dU(|vNfQkV-WRERA(a^&7AQ3mzEnqF|}JR)eOA|WJcYXb^eWAimC{R8{L8L1g35lWp#7 z6!6h~YFdJ)d4ApJL(6fxY0^t?`}Np;y^`ZO)W4;Fk9foTqU!Q-yDN4I!)=2(V!xIL zT<&0crhHPG-?+Yna*?Qt zpglydPfD)iT#RMF+~3b0Bu(CETn#7-IG2UUYxe=yH9jK&k*J=}L?PsZkt&d8k7NS>ANIaG9P7UQKcYlK zw^dSUAQ2i==GD@W5y{93rJ_M0+f{cdLc=I!G=yv#l+}>PijW3M$Sxzyn>aOR0 z?&nF*_c(q>|J=uQ7hRvv`+d&W`8v;2sk9k+)+5lgy?UGSFRhaBY{?>Yc(8LzD*0uv8&<{a84QDE?8kE<{C zbr5O{6i`6o#m*}cf%?658YG?fDa}(5O=b^snBU!;>O|~MX&E>0C~08brROBzH3_MGcEh?r@@l)Xg993?N!f$6VOJtS@gVKT1nXL7x48GEG=kfB8((#T zuw9XNcSUk2_c2Q-`XT8uiqyX88ratsa$fmGEAtos!A&nzU}LfC(1I{)5*?IwlAz3; zSV_d%0X^!qUFb{DR*fzOA5B4vlHaL1rh^Pu5uQLKZ&fLQ;YX;~3O;o`lMeg_=>v|a zC(S7Z?JZlX^8y2pt`3Q5SmTWfQ~J#Q&l#FP^5S<3GQ}ZqaWgx6_#VruM`}TBXyKg( zL!xbbm)*b-f$r*>T>n;E((lG;sH;aRN#keAO51@fE0qDRpii&Twgyt>O>FcNH{}FP z*y>Xc9DdIIQWgbf#pbf)?%5f45jt*vQzy7CNpib+*-e;B(pt|NffQn|w zgV=W=@X$prXYC0bw-cRf=kwHJ>=xO9cQ*fGE+`_C9lAfcqnkBS^YsAH1c*UH86hpP zc7ysI_o|N|WeIq)_z$gd8<^M1p=3iM)~eiEjDN|ypXHWCBv_b|YP^11E$J3#AEU3g z9FW+YoJ(LKb8v@#xrXa=PUW6iWAeDFU0Tb(v&83C?f7MfK2)eaSYPvQfX+uDCT$`3 z4AE2Scaque^Z0>e9vit^B>PWCi z<^iPJDsKvgJD&sa`LwQxTTm;4_BAdOr*qn6&spn{Egi1Pyh=%EcR7@H-Mw-`^4xOM zINxSXV__?MuB>CvXJO~0n5+A{*srC8vVJ(z+fV9fhaa#g5YS_bsAy19jG~{0b}L%8 zgEjTHZ&zQC%kzX;vcGYk(=OJ z4H=Gt6kK>|tC$LAQFCGxMC@s9lC?GAFh#8ucBJ8dM(h}44l_yjeXe|pi3;`-f)yW0 z$pFc4`m4kPm84CW0C%ceR5sbXIm%_|Df8*E2nvUjNrjWZS9#3{i=Tsx5LeHBIT_%{ zBUn^lMpLe%x*-+y2zF;plzbl)iX`l6T04oF3c3mMUF{I8Qin#P1$X-pN5rMBI5H+3 zHq-yL!ke>*5Fa}(HG3`w6-jRBAmcg&jb*%-^r0q@_=*F=P_)Q+kRuWZXq( zGNw-OBNye|l>>s6%Q@89ZjI5x}>BzwO^KHVq;;5 za;!@G5)%A~HrEiUkJZ*dGi-z}wQZCaEWG{>bN>S1kJB^o) z-kS~bv|RKiLXj99CMHRJ5uZ%n5zPk~*_R<`e2WrR-VUF`JQxPhq#D2RUa{cSq)^g2 z69v4_WyC=wcqy;Rpmi{d#av!vNXA zm;QQ&`!tQ~fqqPqfxzjJc}d!o!w|bpw|ai**EM+1}220# zT~cWwb>(*MB|BNkV2x*}U}guk0^eH$wu>9aO67)B5~p*ZR^fc5Ckj`KS$V4WZW2WEjM-- z%_ogIhL$M$E8lw;<_TIn@pYuNQ`q{xpiVbH)!cv(l?K%Gk~CijBUL|qx6s}#3#m6} z>gROP4eq3YNsHB*#NDYF1hb6rp3?R5k^Rm?ByzEYAlgQ0&UD*vISr$o?o$`@JNC5( z`l5T5G&>`#5RUh}u=kFjBV1EWo@#>qruEb|!N-npT<0D9UMf4boL$ttzWb6`M2hht z2f%bUs&_R$nLkvQw)5(lVf+44(hD-2hVeo|>mU|=AqXZ8~^L1}%bnWvO{&fF34PZ@)WQQ&Z`KAYvTXLYD>37-mroUFb zx2%1{B9$+YK;7oiw2Ii%ggo34c;M(2Jjw1DZQkZ`- z;jNOp59m_1B?N*Z1}5VL0-j~4%Ul5i!%abLTQX_ef&ud>Avb9V*i`uTYc$RpBCaH} z8zf2jD57krT31$$eiK*p3Pxtj&cOG)e;#Yp*MMr02*-{-nD2C8WJYMv6}09d8C!gQ z_hl4$qS^0?Og*SqZ0GvoQ+ZMOH<&Z=cf&u9hB$a2CI>BTZ z0S$Gt*MGy}vpv5Po`nmnMprm_Q458G;1dC}jUdC2<|kVYd5d@}lC8jN2NyVaq4rMz z1way_@qviCfiA6yNUZitugmq9TDli-1fgSsf1NJq2Ae2CNH~eF*^ec2jq&$HpEMbN zlcdY6tG_w4XQ*mgkG~`Rer__pabx@rzfd!{JG=)P@799d$o8Fja#zq*{$peP-v@g^ zoXn(0gAfM^ddWEzjl&6uLi$J}n_G^d`QhCDwh+1P+bX~oQE?zm)fmR{t_X)`!@b(p z$s!Tr?dl#LVWWd=+8hnZ>bs9Rb=)oAe(7x(`!RcsYiDstfA1=9Q1(2_DZsI!`2O#s)?&Lg!}aHw|XH+xHTk5K{vJlF;CS-if`IT z>$al<#?_?*`VQ3903oh1o}}nDfxGN?1$t=^r*9_5;SJuvlBV@%`L}!Cv2G?aM8myW zC0?4bAxyOmei1sfEeKjkX(XX!ck#pH|Gj|zONrN9!AN|=l!biiPU~91%`!*BMfWBm=e`00g-!dCYb*24^o~c~NCI(ExPS$hVfhq= zHdr#03Xaw{)dE3_Ick0Ei|5;ZMt`sIy_z*uq|ilrB~qhKd?7j&gZ-V9x2EyJX2GM` zq+D0-j98e{3-Vas2`i~g7bXWyr zx(KR;5`oF3((T@7E)}DJ$x}r4&O3E3j)P?I-t(PT+*|Gtp3FsPa#d~x86iVxxK4T~ z=0H~Enah-dNj(mHbg#}7GLX@#%%-MCizLuRGvMs1&(^&{t5lUoC!z5epV@%Ys1cF+ z7Bl*Ad6U^5u{a{1QdzC5r?QRxvhaWbjHgR5GHe^KLx#unN@RNrtY?rw98MxN zGtd*{Z_<`Mz@1hzAwfN9E~sC6v@*m}Fd8!iQwLhyoph#PNLv?V!%v2gx6 zet1t~hmghjcrOj3=Jod)A)qzd)1|*y)D|(44}W zr*b+-{|sg;crEW>pOSoU%(QoT1ZQ;Nu~_G*ivzeHtG3Q+(|K@bZ|r?7nM(Lyl_WN6 zmGPA8I~02>tN?aRLwMc7qsoEck7#ux9$Y}?_^CQ@jFGx_st(mOnL2dqMCjGs2G;r! zQc%^wawInzqVOKMZ=V^N9g2A_-piXdixw&Kq4%%ibNT|o{f=a~OUbrPR9`-af+Q=< zbm3-^6xGQfJc=lcL0lB_5%t@2;J=%MlMcK}Fpq`G*0h^qh$j^AB*NpGUXV2hN{`;$ zB?x3MBaBLKBF-5p1wp=gZ0gViA1!*oG0?GV7pX6F`dzKaWv>Vu@*?y9_zVy^-t!&1 z!?|lQeb8SH(n0WPR>0#amDOGIpmJ#05Af>iZ-OOGvn?sjms z!8laf^;gE+`337C464{jf$Hl1=F2T);3~0y<7YEY{e&^9gihYmM_QHL7TkIqJx#*w zoXg5UQY@onFunxof1xy6hk&xYJF+EtsxkoaeeB zbN}c2_mX;Ao@h~!`mp zNSZ19Q0_cBUYgJ_6!y&T`k<$axJYku(Of>vGySS(WCY4gu^7@J-xYHbw3q|?&RI5+ zJt%g?_t*UjCz#7fnungla=7KpW;`8|-++)o6oum}Q$VVURjFrx?0@(givoiCIgE~6 zKJ2G@(?(A+rh4Fo^?T~|`?F@=MiLKv%bW3BNJS$szDFQuWIemMWS1&{--+OFo+$(A z+t5<&lx_E?Mv~cQH|3$v4n99#)zX8I(~ugz1>`N%s*8&bTZ!qv@ts`WZ2?RA*&_xx z&qC2je*h;_j!nVzC!QQX@$hH4>$bIIibby7w5u%1hEuXU!Mt+an4{FTFeM9xJ*r`F zq%c7b;V#d4&We0PW=`Py6bY{P5e&t26x26WzbXirH{LWFKid{ZT5ZAZ)2T}yDCTKJ zwF94wchEuctQ$C?ZiJ&n6SC1Iy9(yMtjSz{G!+lnJnLveN#xess;1YmN0XS=7z<`H z5`&OC%~}?0y_KE2>*&m^^)-H&9rrGebbG^M=Ni^V_+=3~FGhzNyC)0y>$1o_UX0Gk zhE>a$sa0!8#u=yj2wQMQ+T*5K%-WBBpLEPpys2r_`(S1ttKgDYbGB+y}Yu(HbsYFBe7Bhl2@G zlaO|k)2(%=P?&-Ae7fvBp)?VmC|EEWdztLE)d;fNKRiB}a17JqgtcnHC~^asBf;_@ zB9t`qJ;iw@X(Rp&`S6K-qyC~BDG?tD+_exD)^+z-1BK#^=owDwtuN+Bs+u}Fy~ zHmwoeSYM9O+VjS<^p^McahXQM%E!JD2w(E)hJeugT?^DyYSGE1zDYfA2#P$PhU6h2 zaC$dt=nZog2{D6*#<87Gqs#`j)uyB@d*}y8&}C|_q$jvVB^fZxZmYMYl+2PxN8xdR zEk06*0r@VizY=p*LXbhmo8Y?BV^*w%>h zuT+6|geqV}Cd1Tw5QGa@RtAa}x)Hr#4RI2Da-fj|@xHL8j5q#9hUK1!FyXq7$4u$) zyFd(7Tb1c*KvO$T1_qpV)Cd7oFv%S4Xpcy_YSHNANkH4P<58tg*E_p14MeL8<$%=D zMksh-9`1Q}57NDyz~Igzu;)_=_CEXQ6JMPtmzl@%?5A+@rXp0>Zt7KhJ(T^Ech@pl z#rUM=6Zx#3Mb&5cx{&xmK5+hk_m0-zS-i>SLE|WjWjbEv~TbAcUn?S=A@tk7r7rj zhZ+fdy0!ds-`fXowP5=N)4U9Jjk|iPo_&UY-vF^v#g16chL{>M?v>?2AKu)EU}2>6 zNoE-sJ_uZFqC|4)k4PgjfF`izTv+M}%8te^S27!ll&cBz?D@e$#Ve#y?Nck=4cL1(U=AV;^Efd#td)-eB}T=SCjJEVXP^BF}D4pNgOos|VH`l{z_!?#4vZV${ozTF$eW;qxT zADI`R5|JP`___m7$fcF*tT&|64N&ArFW7ZmFyCsgNRdk1qw{^L z`T#jiJ}nGr+vEoz(zfd5lpQ*z;763a2wo^Q@`(;P7nOK=(qYV5KxTHy!_8H4rG8$M zD7p+2`Hf%{OIr~oRn59AE;T!Oi2LBm0`j0RnV!2Q?rO|O!s%4k(B&XQyz4k$3+>ii zM3Hv5D&W6QHQ&aj3bm~4+;w~kl8a?NK4dFx?Cb5$fDhD+$U-9e#7sj|(1B-Okr1<~ z=)Q|2yCM}XFcNf|mXQvA;d8pPRHmT@SaM8V>7YTt zog4~EW!O?O2mk3!(~_P(uv@31elnS4Wf_?PhHCX%_Hcsu z3>I4FH{1@rKZtDD^q!$MxV_xOyb|bpFW2omwhTUG9T=Z{xX~}V-n$GKYQckMp~qt_ zkz1@m3~xkes!a}HwV%E(V}~*znE_d^2qh*X{9lMqy)rmQcgs2|NSu-clQ7#WNa-P? z_#DXyBy$M1Noomjax&+HG!MI?O1^!#^=GfDp7Wu)N>WuNb1YFaWhcWHP>HH;-0ha0S%E-$#AiIxdbSBOGe}SF zLO=kj4k0nQq)waF{Or^aq@hm5eB%rt%JJ1?yas(1ivsA}WPVG-5s{-+1mysX@DRk8 zQ{@5Jy+KC2cUs!4sA;Xq8z8kr`yMW|M%RvNp$x|EyhXigL41E4eQDv8LQMsqwtIeU zZ~QJVLSc9(azb^Rn{!m=z;&4Zxk+FIg9BQuyOWiak1I)w14ck&=1Ch zpqM-Zc>qT7Epd^OmUUj-yP4ILo9E9blpeqe;se6~NNoVNvbvqHn$}q=_+Y@zYM`Z8 zfWeiPBOK=;9fMmU@o!y_*h7Z+pCARBx*F2@i6J!0Qv)9*6Q;*FTZcb1&444gDF)9T z>A-Z*MVm9II#+{)%LZ7+jRZD?Gvl&v25>EN(p3dRe)sLh=NrG|LewgyAkgg*!ObMK zoAYDHoSxVNd5DXq!L4cN-A)1+yba3dd491;wPm55hjh#1on=X#hBm%fi8DCQVP1SA zCS_d45qHLeMYD)-|I%$eH!%2k5qrTGuK#Q7#js7J1ZB~SW62FdgA4=&`qi2nj=7T{ zdw{`g3G}NBq3x7q`whYz(N^ZrFuN;fX@BuWUrrnd%q zfcsC#h(S^-aV?tzsh7=rI*^)b3>tLdM zB@?+ul65+0alc9~kt2?A!k%cBJg{L_wP4jG3IQ2$cQiF380*4)&^ zLG96>RXLK~JwGx;LYF9eCP_L0zcE7JAE+t&9ukrdGAfhA@PnjD=EIu@@inB(2O7T07bDACFg*aq@W-An3e)yInXXzuD=x@rr?}06I1K9uW*#KX(cx&{=E`nADZ-3(R z5j`zC$YU`{SylyL5}mhQZ%)83eynF3s4uWC3(W`5kvLi0C4&`kP6PdE(gI2sr|c40 zQ$@lI0DRkh;r^D!zY@-hpdqOg(`|FX2ImpMhySFLnon>?Qouwl;%o_e z>tFf42l}pa8#z$Ar7v4r? zKi3B*ONeoXfE=F!{NIUb0Ac%E`J~6cX4AY*%3AI>IEkv?J*X?3V zzaTVXP#AXdl=f89K1=KsJ1-C7&C>EsO0lQMrHR(JOw{{JNSCkZ#yp0G~u>=WE(E$&El2IAVY(`$X=)iw866N z2I@|fB1nKnj-n6=Om@?at=CSL9k zN+jq0n5d#BM*D}Qbp$@BcsTd)Z2fo2t`d$O8PoQbDLfi2d3zV6@J z{ad?7z^eZ?=ERZxi^%P_F&}n0KmXzX#CNCl8`U_jToC<4&e=&M>czZeM=!e@2;NyI z>^_xmdlUPMmG^=+nXg>z=W2e*tzh!1yyY8C`>^QFt6Lqm?6P}M5X*%2i5!xvv{c=$ zI-PQw50Q7&8=pP*$foEV^8y zg|EvN0O446sWKumgM50C;v+LWeMd5drFvrguhG}Ya5jC@*~v0_2>uOZ5g4{6+K|BT z2Ml?XO{}aJk_;9NX>*W1`C*RwUM64Ahk-)qhH5~Dj8_sU+K^c`*!?EiK ze5ja%QI48lL(=2U-Nvi);_-Fout>Mles2IG(zGB(ortkAGek!;5T7|G=EjX@o%IJL ztB`sPUbw*yx{l2AVfJLCMlwL4=A)Y?6H!yZ!^Bj-*)syK{Pnx6sF}v9k19XD7=7jG z`(&p)y?aZCp#W{pL;VgG?Rtr}A#J3VMwNyP*xZ_re97Q7bRc?K@{!>m9?7Q~dc6FT zNK1FfM{c5L(EbAppeQ}d1bBliU`3-#6DSX$lW0C6ri$nZA+=BGQ^ zqr!%dh9U*npfOuJ1WD^EB&}lA6$^e|_`dTw^MZvJEU@IcR=E9vkLM54J_@t%9;>L}d;k1#Pb$_6l!DH>Y z6azwvAG`Dq(bXg16TTI&AVn0E_vGr94E`}Sh2fPz_nFOvZxfH^{=nB+U(@=LyNY$} ztQ*fC4_`6}Tm;bpxEOOR0_~M!2vD_u&c~JCKIcfh_zQe#Y4t11p@mlO71A8yMAwrH zR_rbkmC{K2vv_S5zCro4tN9g~<|Y&a?>RE-4oBC{ z6T?dR%RU6nq5?utxgEQO(LbX{dgxk)fzy%ypmLgAvy0$+dn=*xFY|{-Ud(8&D->*@ z&W6#P2W9Vi?;A-X#J{+c%_2~qPjoE5N^SpoBlEdf56X5sGy3Q4t-A0_Px8w*dlQS~ zkS4gIW{g<(s2!cv1qB5zj_$3(wkfNE6sc&y_O7a`Qfd_v5&~my!JfnK{r<&C{a5Ck z6Tp7%%9EK2LsvOQs6ux6^QVUv(jVbCg~hYjf0VGzmeDOhsbLg-(YORnjiss+Mvw#% zG!Elj6;EoN9XxC566o#{?W5y13-R_Zm&INb^9OCroHG8;=@_oF?T?MxdzJeuZ|XZg z_}A;Ck=!5t6@?Au1-~ir{6ju!7u@x(8_aB>TPS@8>~9>-tvpuCA#5udJDO>;K!u_1 z>viHI=(4E%fZLZW+@Uyh`~~n;g*!R;RU(~xYCi9T6`4>dq}g`Ra4c)6Qd;_*JEwOa zAHn-q1o>r`VYc6p;kY_3LHXL#`5S|@={oa1K4q5loOVqk=6&tMiM%TQ&Tf{LbBb7p zN--nw7=Ea*6h1sBKGcG`j%|P7#w+UQC^zX_0xPd@9GtOqW3<83!V}X`)1>37uamz% z>uX5!)7rTNw4;y{3r&aCMW5oYkUcLSE=JwW_)acktz>c>Mm{AV+@Q?EP?jp%Fm zvdx`5i@_GeKU%@|o};Mh#0Sf;e(MCW8gFF1(05@`n=k^eiSxg8eur~M*!z4JxxLpy zz{P{3F#83Xaq&q(zfz~qg_R=~#G@FcUnE9Ps4l=>znyY*h-LikK=$~F#dm~F$ z{3`+Cy9fU6;J&-SuMx(N|B7nE@J=r8O0k1H?P1Snd{k+Eej~qWvb+41xUTp~BN*rX zAZqDJB>^r!6HSEq^KbU|kIcm7zz3_AL7f(6_0(&VGxWmA9i8vo?440s12fzdXLqla1d zF-&*_H!;5Ebq34GBgE5Y;{W8I{ZD>IY}8PxC-*``M1&WHI*zYsOd6WPkwu*Q(G2*)^gzoVa&7tW?BKyuha zv^nfZ+mz9)s4P3rI>b0sS*8b#LFB`SBuT2}AIRohxcgYxrRDZo;_nilkCC`wtQr8N1c{}VT zO)4YyD0P!rH)`bxTMiBW*PH~js$IpJpPJ5z>j=aOMp_)@ceU_$wQ%Hw?{~HEceU_0 zO<=(O|E3B2rV0F}3H;^<{pJS^@q-Lv=rILsZzqOu#zGq}vRkXZYvRPA!Ak#a z9za{Wph_?>sNGs~ZQpUy{os7YMj4F%Vwfos-H_9}U zx*p)9$=T4kfBTh%0@=@1Ts3_khWT4yPI=Um(upJ4I7*dZls%U$?yvOn&wTNrbSO;+ z`Tc=do8y#t@>o}BQ%O7)o@K}KPtT2{nm?aVw!hr);%0bBM+){ zX^(7qqTd(S8esj(d~G788{64*NHr(LW3IEfzc$(F+Log) zL0jDwJ4D59zSxTnpGy5ZdS2DYYV={%c1KGSmK$qhA#GnlSUWb+cwrf8xNq05XioU5 z#|AsejkD2WCeU*ZGFlz?VA&^(pXCTmRYIlmMvRWTFTJu z4jakb`pbn-XR#0l%39je{885D@g~ZhkCvu9wJE`x?@+Sn_)Y%Y25%{_d5<$r|{eHE@DfamZ2WJ(;datCR}PIC&CO=;PsB*>b72i764g!@$*m2Zj%`;gurxGVJ#QrWo!=j*COc7!_2X#x z05M|pWRY}Mh-`aUZubnEcc-f`lU-yjcBF4>|A^foK36Kt2A;z2Rh;>na z;yaQS`#a%?`Jy0fw3hl312%zeBKvHu%Q`Woc)pZ%lXQe^6ZRro6sc=i26TS9ovLfq zT;`x@X@CCft$cq^Km8|e^9VOwoECZH+)#n-@Y`FfbNsFQ?sKK6kX2p}>M&+l?@{6^Ei)cyEH(Da_0gLK<6hxD6w3cDpico4- zQS^?wDlFO(!6@8D`;OCVX9!}7YUakg?mvF|{{GHJaq5!OJN(V*T}?BQZ9U%-?bI9I z+Jpr$a#?lFa!svyPJQwApW*`>?W-QLDGP_EcKp%_5ELQeHO_t$zRY6Wi4RRVIInHitryRz%zoBJbVF-8#lX?+L|Yq+S65%+SK*|yAOTQ7L?nJ zh$IVJDWp;nk{)sh+^~XP9AOW8S{Z2D;F#}yUY8_ zY6te-lD(=Wj$~)NxBIlER(*~I>jk`1dmHz$l;0imgSY;>sOWn~|95}#uO5)B!g_+; zN+q?G7$r|uq_w_iCVK&}H04U;16sogR48~>_Tfh`?^ zSjIRHTT7ell6ekfFRZG#cf?d~CN_RlVRfy2^FD+vtCt^qud=)@^a^K+^@Nc-wI3J& zgBggXT0<=ZY2)}HW@>oUo$Ie0I$rzhHDN{c&A`;ooPAb~shEFnQKDZ8S>T&;<5MfKc)87FakV!#gC@JCh!5AEfAF?{YZ9sbM$Jk=4fDfS z(>3#*;@`;ay^ZeHxZTdIFF42VaOV8;&*iJ_7qBw%KBr%J&)_!OxWi|7AMbv1{@iND z3pR&%SB;`F&>j7g!qj$_b#CN(v#Z-Z#{aRxtW2^a@{VHgbydsyS865+<+|aVSL-f1 zHTS>l?=4BJDSrJ=Sjzd+=z0U~t`zt2xaSs5+aY`SXu$H=Q4I6v?9X2@HXWgEN?9k_ zW%v>(pUiNpSvq# zy7ftz%@S|F=**uvmjH9e%R^j%e|yA1OX<7hI^-jJmvj4vEX}rOV39dsNXN8!Lxo*= zw_Z=HT>pWP16?Ify(OPpd{cG8I{n7~(wNY8PEh}Wea~J5UU_O``yhL5O#Wg4PG?Fm z16eD2sJy$3i=Z`kZQAQgMD^@Hraw1}&zUc-fA-ggIr|veoSNLVyALdpRNi;4lZn%| z`l0uP57RJLfO2P9PcQ+Tj@%sm3DFa z|3EUSbu5SEg1f{eR?(L!$NqO-tcoMQf4M^=?JJFo*DR(E5F`6aLK?nG*kDyu@ktx_EJpPc=O}vqn2vlYU5W5BG3E!kL5oyUsTS? z9~?KhvAW|4+fNpwN4gp%RZqnvx2M%#SW^6_m8qD z$Z?Tj`uiB<-MKkJE?R8hacFz$z^1}Z&#*zX!uCoVAEq`qh~QIRCgV^mEUmKfn$R_e zywe%=W}KG_o|8}5&+EU4VK7JkegDOjt6z2Mt?HOVSQ|waoZcK$BeW%AUKP(=MQ2JM zJ*7+SPJ;NOi z9PjPb6aOw6`Inx?m>)W4tNqn#o7d7~E9A}M3VUqE4Su3~pMi;8%>diuZyu{zyd!(w zj>B=YDM5LRI{Vs|Cpt6CCnwv&C$ZuO)@Fe~MX#5SP~teY$2vGqV)cF`AAgYrmDyB) zgF?VK_Z2OYMdm!G^Y+-`pP>dYCj>Nvc~vs8u7hGKrB0-g;oxyG_MI)uu&mJNd3$tF z&p38{d`gc#J^n`*@z+z2a6x(< z*td^>hh&MNJJvECt5L}I7QoV;Q^X7?A^I20;?}P+l1z}MH!X>ZFht#mF!%|O z+f<;r%}b+sET3&M&k-*le-*B}1D zMSk~tloEPwHSOucue*yu2QvT;h)|2!i7R+5{?)r3ogjuy>gN_3J+f_`wsfyf;uX5t({qjdKFh#6QYd==? zqd=F!fu~nJI^SyrYf&234Nk+Bpm(+hx>3fNi6u@V#(igCxTNV9o8+EUQ zj}o*N&dm1VJ6?JQKVF>vxZ}tEs@p`JdAW#_xGyq~>O(|G!RPhWA+Z=f+aG+m!Z-#*E#}kLJDp@jTPkzvf0sDmw^lAyO z_*OepmNPa%)dmb*RhGBEl0kfqgxlDe_F~7zPgTtwm*qE#IuUOp%uLVFr47Z$dOWnprA*#6@eU zeGR*H5=PCDKY2Xejmz4v1S>&J6>~|AfJ*a+`s zcP37N5(H2Ezz_#M3FgWs)NN>b#PAv717#nKRKeEeHIsQy$-_wRYP*PO<^NWkR5<9| z1;bU>w+{VV@5pt`wB9t%OADDpTTvm6FwIYcp{c}9)7m8=)2*J*nqmK8g<>v?`oc`s zP#;Rmwo;i-&*yB3)Gn%hsDw3(P#gK1{CgxAv{i8MES#^s#<8!%C#vf#j_=GZp7ai+ z$TfY1GbI4gT&9}7*^ho`=%5D$3vuGkjaq-se@q>GR~0?FlCxEj{@LT+@ezY$Lt~J( z(I<6QUx>|-c=-_Fsbo$qstq0}&xMF}ZuoN)bvN@Xz;(J!;-klmKW-9W!{60juaT2^ zfrHA)x%KqcptpE~utQWuPs)0Ro<8~Y z%zTRf@UEaRX6M_u<^XJ8gV*m9U_o$U|Q_%9bhN3Y1CiEY?AmPP6x+X|I4 zDm~?9DENx(S=xtFo^iJ!PQmQ^r$`>$sa84d7?LIVT3IDi ztrzz=J8a{#_+aaF+RrSYUS=V2{z{rSevUvro5}ipBG+nuS5i6-6ASF`sj1A6>oZr&STbeEB2pG23ge6yiJPsmW8g}Zzh22j zJLh+z%uO*5Ci+*KqpJJp^T)~qd1Q9NjcaLSOZeKE4hKWd>L*%kpX>DW*^yCH3zDs_ z^*3H0!~xm6VOW&&gL)vo68=8>&STe6RHo(-O~!wsRqB5{-*fx*+ox-1Hd<`x?e2(f zH`?93VdBQFi#Hr1b?@GKazO4=tY`WhPia*?kpsi`tBI)NDj#eqCkY8OioR-8&5WS% zNtY+gkX{yG-;(*NmBhZ03u3^HdF09=B+2`E)%eI{k?a(Es7{u~;;C#q`5lf;fhtM4^~&OjQb;#2{tRp` zSLybXb8_^ZyDzoZiX$O=$gookro*!PKrOULR;jn;oBP7j<@8CYa8Igm zlGI|(bJ8{z*?!{TqwgasPghDUf1$lYIVb_|=DKy-`N_PjZ)_jTAM$;)!EO)}Ot>Hc zAe<>01YMB$DwMQ8H!O1lAF@QmuD z46cUHZZm;l%geqht=)KG9WM*LIwuLUEos8+#4&_C@%DAIqjkvg2<@Bm$4uloXX5MML%ab;(UHSVug z>2cl&#s=2${6&Ub9Zutc=L zPoMX^8_J-qIN-oaXw;3n0;hFy^u%RBt0t}=O5+$@c}X58D|WXDiq~|!x1&6jNid5# zirNlK5+jWS=m-q^qRs0)eqbc73&Abd6&GhSZC;OCQ@?&K@w@jJezxHc+N&y*ox0Q- zl3H^=Vkz{^3m?`C__L!(uxI}LOo2cixt=$ZUb=TuC)x87j?HsO=LPO32E*S!9NJQN zSHeq&Q)m>>)Va1WBx9<{#iz4eh-gOLsk^RofbAm0Xb*j5hNowEbjqF4k4sGgr=!w1 zdW|#X9_%E05~W}cC5RuFv3J7=;&n#vZEZ#^P|}Pud=GbPSuLvk^V08 z?91Q-X>?;B0(1{p?+QMm0WNNO<0G=Gexu(K!Lx_P4BNEFTOgjdQsf0F6;1IiwKr{9>9W$#X{ zdVA~T-uhDyP^aTD%J9e$UwRMvpU*G0uPkXleX`teajftG#-5kz%upT+gM^-W|+IXGs?L>v)Y#19X5f4 zSB^(_euaAvOc2}sEL8Kx&P^^nj^GkL0$k>8x|bNtAjJxlE8UzvyEwfcwFuaeX2Ski z^XN$|Z+oSJ@eCSF#bG@;hdV84)yD=`#i#5LJC5~z7(rF%S1lXzPJiRsk%SScF)!Wj zTclQs!cwJNf2*7ks<_!plEM3t?fbN^UxKh7ODcI#J2i)e-N9W_F>G3LIFRS)C|{Y( z8{OqFzDG- zd81S}))4yju^~S1(M)i02*}u5AMC4ly|*6=vd7_q&M*_udd&DKtj!M1cBcJ%x^469 z%GkE_jky$CDRQ_#v@RC}TYlmSV% zOYWt{bRA3m#_t|D{+$8{w6$68w;SKAD~%{k2r%=xYEBT}G2hsz=Q}R!*_}G~R#PR& za}Ffi8rI_g$FADv*u^a6JU6xBQ}*IK=Mj_nNTKhmC6R91su3)+Jx5X62o`Ao!mMn? zYVpamqf9BY7+;moGuHC+c}6#uhxK;3qg~;nt>u&3Kn0;rDMEIi z%!eP%g@a zwEF1O85j26*){gmJJBi0SYsaqcbfor`z6zTMsRSZVcDel@Jc{CoWELWSg4S)TS1daTP4Z3y_(`&-6NK5QQ6u%uT;?23ykP0&be??$SJy8* zP@OHies9aW%!Q`K5wa)v*2&U0?R!@#G9T*aIv-*X>b+~PUX;Lv`qH{9i2-Kq9Qo#b zh!yqOXp%3T(V_dIdSDFlot=BH(+Ru$G4Gsn&Axlgv#|;34ttEg77xu^4O{Pbd=-7h zdM$7N;LQ)k?|lb>QR|oqezFiu7vRwxLR&UlU)~|_Ou6=#;!UCES1X3oNxOqrxKZZ! z86*I9fLz-GH`-@;34ToN@91wjt9kgHP(`17O5bJJE;#&>vo>`#Y%kl5Wt-GJ>ulrd zIGQ?p?DekHY|)(__7xh;6?g8c&Ju-}n!xltnmf&69`*3JLw%ePyaj*;6zICW(|-uL z>d`kTSM!OA5SS0Y$8kXVTe}u|jbL1+6LL!;wA6CO`EigsnHY*b+7DW%F5b~*V@5|4 zMebCDmTJd0fsMQ~`@+&_WqvtOMiZ)hwsy_+;Z0}7%}%jAMV482Z9sUTzn)9TqdwEH z=*n&=afJ4oX@Q=3rc3tTYMS~#xV_!ug!NOTm83sD)J!^Lev^NP1nODxv?U?tvu9Ti zhl|(??&I7&t?0VRm3SC-T|+EocMm8IuLQY{+f%OZP;{=PY9di!>t17J#6nGg(X9kF zHwNAfh_G^STO?I-c1_so@|riT+_gF1cxRPzolZu;nsjvC>Z3I$@~Mc?<&_`iSS<+{ zU(g^mZW+x(46D~;g|yzxq<86jl%P%cy(@+u!>;nn&lF8g`1HcG5`c_-54 zLc@p0pBs%4uzWhi1@M$HL2Jb8lu*Hn!N%|QXkt@X;H?oI@iT)w(iS`LOt&^^n0o2+9Y0y`v2 ziSLvBd~ebtKt}cE9)L#^+6ojFyPJL}8<&v!RBEXS21^sjK>3zlkuLs|W4G9-0$ZJfaE)D5PGRgyZd^km4BAMXxEh3$iH+b z=<)wB$?5ZhWgvRIOegu{Kv1*@$I-y>s{COP`!6m-5nR`j?Cw zASW?{#K_-fMRmoPG7N`&ohmsYuuaL9oORJoQL$k2rT5Fosgqb2hmv4oMVMeW8yn-f1S?@E%d+)*EJ z>DODrdAHMU>?$;TbaHCF|3e_JyC>wX^_-#Gl$jMUiYm*c8Dp>_@=Yhu*BfQe)k2iq zkpOnsIMNTbiIHQR0PA>tQ^u0`=fHB_*2wkS+?_Q88a743Ow(`MU1{r~w57WfA?q{( z9`Ju0?-vcT)7j-l@~ae91cqE$=^k%vnRTX0I+ZEXRi7gW)`OWUe^^=D-89N5VD=A;%Y&Wu@=X%{p& zC=;mhh4I=2ZxHs}*w3c`ub9c~v*`{j!wg#Z%k&qiffDtb@|%BJ(265(QO;gZs@qi; zgCfSU&byd~&(j0~4jD%1B*qoKl6Ywrf9S)#u||(i%{ux-cO^}r!LAl0(6Lk`<@4L; zpb5Dyr_&NwG-!Nr^JdA}aE<7_OREu}a%rNbaynD zKhqo|6o3;=4?l4dr;IvRyD@k2ZvT2j$L+!`W@qX%(U~)gRG@bGm5B;jwELaCkY{?; z`WY=ALvc7z#dUEMbqqs;#sq`qUX*(b(oQT}SZk3T20Qm=y&ZNrG#_|H={f{j&v5yU zqRKNiXt=vB5BSE2GI^;PJUl)*_Rz<-N@r(ES((jMOe;fm&-Z{u9KyvIdeNj0Dmban|R)RBjoAS%MRdslVOq<9FH(#DI+VpnmS_*-tdXj6S&aeedF@~$m& zDO+DP1r|>W`B#BZBD_)k{ZfyE2ktzP53<_iKe2?G+ zZhwPF{h||4Hb4p79F3?4{XS2WYG9ot`#Fferb;OM7pyxuifRdwA_z5&(6ff#N<)@7 z6>6avp%s6v@I)C3Be5N2b{f;}H88P0*5xfVMJtrDrC$c2#+^3_IN{jaRTY5!NngGA zylWKej-{mYXh3z&Cq*pcBFR-Hx>RaeYn(?~=Txb%@nXF8QMBs@ue)azdNtaKw{yle2byNhpX#aqaf3a_t3P-7cM z-1jSTR_Z@YiUH#Dw@zImaW{7{}>M_-I=dy zFrW`boC#<`th`aERDbXcDU%x^Cn_2vCIuj4ju^6}W$n+@q;hHwvxpoQRJ+E`uW{n* zY4Hwe((E0DMa`*m6FsIb=AS6n^WjB_&(^)AySnn^`t4use=y-@G~yZS<>|;g?;bDo zRh!9-sLbX0N)nZwAjbE&*3(Nm%3E&4FkbYj&!p$%tMszhnEhvNi#|#FFpC5xN~} z$rNDmjsBD7Uzm4{7|hUBPvZ&bWe9eG?zKeZycuX7Gu5Bj_rb+9qqll#>InqAB`E}> zv9@UfJE5}r!afm6YERcX1TnIQIgi1@D6+%Ctj=bhRAn1?KYhXTXQ-e>@Jgg2)C zgf|YVqxTnIzVAZ(jvDpo6r97qI}%IAsHB#xW!2}5B>gLTI$Mp}-ajq!PeMIMWgQ{q zJOuSXGN;j1DC#&#xlh?B&e0OU zwvd9Y(bc_`e8qFS^pm#D|4pHPqM!Z9iQHG5i%8*?;uWJ6Md?O ziFGF%Qgjwt{f9i>p+_ujBCL}T^MGM3Xq_G9PEM8rdv_p-(?(_VeapI`ZBUSzHw;O~ z=IN|w4$KEm#cguYTwWUb5@ykxHddAu&ew|IbzzUpEyPiJEs6-&!pSMp*;$F#`&C5% zRS7d~kf5_utB+s|ELo>-`VF@7Y^jDLpwyj|G{>S*KC8yDZ%k{wZNPYXA|1Uha%$m; z-2hm({;I(V{Agc#1bQyenwFuuC4|nqnSh=tb;~c_yFlLlRxV6&b8j z>vJri_Z@}`6uJww(4-0rLPCyVU}3UOQBtmJIurW#`( z#9JxFKaruUtf9)2GtiH3ogc+;;Gdkq!S;La?;RPNaaLJYD`))$z!1TcpCivSN_XX$ z-qP+eWrNzv6JKNP_iPZ^u0Io7R`{UINC^EsAggOg_qG+bM;pIICyj2eDgNvJV3M3P z>XBP)*x1ZGU>S1Emk1WExzno)yS|2bFx+jfVc% z!e-wchP1&$P#E0*67ND3td?j&2>6e16NQ+HL@Ik!(JB0?BErSOc`P!g5D3#tv`^G=z_ucMk4y z$<##-Y5r`}TJ-}3J5UeX#SoR!RX0V>(cZ=0y4<1T5Gvyeizm_CG4H8JT-X2;o_Y}P zdbRZc3S$giD+#hR&kc$CJ^%@ywnXXFbSBAn+3un(7yM_?0Y#m`w%}dQo%*P7#SRkj z;0-st$pO(pl?B00UCa6aio&?((c0Hv@qf!QTQGZ|9pElN5G9klpwUkOgV$}{Iet3& zH5{7BwwB|$mxLC!4b^g8{q7{uIIH(_h!`_zPoZb9U4Udby7nU2Ra99*gA&L-jF6wa zEOXKy{&jFUZoxm11*@~}yqanA(jITNvML9K*EsTKgkM6b2coDQQoIAf9dX!$_9=4C zev}v<0jSR3cK@%1T<&EN-3h4V8us^gh^}d0cTE^dty&0<87E{Gw3z-*)fl`owAqP_L4t**$$p9F@?0@&&&~e~g z9_vBP8r;_)a8HJ0)=qFI_B#SG_YOW&fGd82FsJAJ(s`Z`XSwDloMo2qscX2bBylOx zR_0SuboAS~_B+KjaCpg4izmnE-&Bgs@uIye2JN*5gBbcmwv>v)EQSD_BS&w$Mx12@ zhHjiJRFaBZaxG3t>r9$F>=b#M(rIq+qqexaf@$1@gQ7JKhGau=0t|^7E^M*5 z+j1XVShU;ET$p;$RB~Zar8Zg`RPdkX-9->Ez_h=CsQ9|&?mc*98V8v#Nk3GreekDf z;=41;ni49t$KKzFU%in=e0|1DCwmtj&*jzU5_(#mu>7Ch*u4B~65f%abEFXWYI5Not zb+w!Mi3(riol(>V0MU=jG#Tg_3ZP`C%koJ9=P?1@x5PaHWPF}P^I=-qu?XKCbiXN^ z{by)&kQu$!phq(cUy<^UUTo79`SiY3`v(YetK#Bm~aO?z!Sdqet>|Ri zIUO~U`e!d-7-)P^kR`*{$|zf3VPGFS>8GiuoP%FD%6SM=9gGSUmo}a%O#X z-od6bG_(y~JJ`ej$J|>$RlRj@qkZO{a9-`Qw!5JMZ_M_m2C&cib@;z8qiK?EPD7&GpP@KJ%F$xX;0L`4d4P z+#CqP`9K2TG4j0?jlko`YM8!nZK&_HCo%fZjOGaEwt-$eYXx&5O( z3Nds)6S*7yyz0E>vC1`mD=%;;L-09L8i*vTVs4=Ir6?gye-CN8Dju4@awl!`-EFIY4h>ofr>1tLfvp2)1=3x{1nL^ZaQ1U(f0%rUvV83+o1$VV26^0sJDQAY5`(5QcKnJ`3x1 z<+H5HnS_7O`CU(~f?~T4XwPv(feT7OtqLi85TB;qPoe=ScGV9 zJuor+NzHoH>tN^+>_f=+j^+oVW`XSdX2Cnbz|GaqtVrvn&i3k$^VS1E7@Gvn3w8k% z?b_yz`x^xKKziwR_*&6`k$s|iLLhW~nE=2W__ogNf~iN6rx}@{1~Kk$4@Qu5*%@UZ z8+1X0&I3-*0cXfx!a}dtryMKfx?7rF0Lurvr59oOo?q7TxUR3R3-Db6exOkK=}ua1 z?!V%J!Oauf!G~MHlChfmv&tyrOO9R3dj`V0s;<+bw6i}20^6v@=U1u)jcgD4PzRhx zD)(!}LtQhU{!ony2F+Bp!MjlUyI%9mgJenA2ClFT#-)*lsegw_|DPS2-6A^f5l0~==U?W^c7J-Idn|1s-wQw}-mPa>inp&x_J zjByAefgp`3+#P6%yfOjx!56gq(-I4nNgN%ru?a-vY{|o$oXFEa3b44OOx@V1?|xL{ zZ)o=o%e15tDi79k`%n(Oss{Dk;QTN>yB&Eit%tV9U2&f>j{euD(!wY=2~Q;mo{G_( z=E!59+lQy}^Hej;-s1wvMB(4h16C&U*t+|=v9UN-CW{s-(^#+d#h*tEpm8qQ5j6Iy z1PHz!Sa1m*zoA0a!O*+t30z_**SkkXxsKK;j{a=>cLrhf%p`1rK-A$bDmc~2X;25Y zdH~3o`H}K@4)?Q1<|P*!g3b>vMk7K~kZ26LfndKfL(kwrm>P5>@vhVEe<$(?Fe@5h zLiw^#4!A3J*2JFs_0vmVlJkR4k2SSj_lFkXJv<3ZFR2)~*AWuG>4PLl^JNhdAJJ_5 zxto9{PxS2D?a9iZIQ=dYZkNnRPS(Hv|Ctlz8j==OSl8Egc1}7?r!ra*b*x_4i#6zRk8)%?Po@2SuFngaV`ak!~i79 zFtXQ(UuPf%NCX-jT_+P}8_aB=pp6Tk$FctH0etj^&z~))XTu0Z-Cae_S;KRGz1|1n zWoLRkMI92lkf(4t9DRqRg!{Y|DbV*jO$9$O%7Z?8$L;IqfB*agWV%oj%m?4eMi33F z5!nOEYN*Dh9hw-ks90B;hcr7MxW64%6kJ6cGR=quAgv(&S(>-eYD4c z0pnrP%kaq}$P(*aM%EH#?^gign@IK{%sjkmM2>-6YANM*_xp*b^<{dB)< zGKeG=UhG^zmwlZVSn6SbUc~<&1@HpaCPYdL)`nTwlu*DI*c$U8;69o3d#SQFc`rO^ zPnR*UPzd@_dKwqrcWxujeTZ1PjA_n{*|e}n$!aRbpy6_Qe3#jEsW(4?+vGiWRi)!{ zKhzp%U^z4vq7yj;?o+;HZq$T&x%xS}<^>te9eI2t1l|xdvpsmBxp!e>#AV4IooN5f zXa#&Ea#LQ90xxO>D$U+@RnTLAbcG!hp`}~%2Ev02D!-@r_Rv*aN|vW-XE6z3{FjH` zB%AyN?o0&^{&sUWa)<=8NK8Z`r&lKMK$*X^M*fbZ4;jYRD|;aSQH4OEml1)Kvwjbp zKPx=@@Z2n}U%tyL9%SCd-RL40A}3gF{c5Ij`F>Zv8%zWx`4NNXh>;xDw9 zbhzd*>owJuK!ub*YrRID=dvX$xxm#{wq3ora~qdAo>=P5+9D17F;HOO;M9LH>M=>u z?hv|^Ds5v8Jb8JL8Y!X=EfqMJ&B_g0nP}WJEC*e@5Di?&{qTBojhm^R+t7zV3yt^6 zW#m$%8;FP=L9uW8&9LpxE`f7YAl$nj2J=hT7VM87AF!(HvhbXLK5I%4h&GjnoPl+96M*k0V~HL12+s5Dj^>(gkWRx z-L+A9^yk69-!K~|27c$y*F}N_H&pP?^L?E0>NB&gH1`Av13$PCq7d&4{yGhiw#)+% zJzpsdLQX{cQ?_IQyLYAezB@b8tK>KtDxnRw3JdN+P;^7O9!)=Xil1))?qYB}M3iu5 zc^{E4K;H*7VyB8&?Z7{U+AMgjxt~UwxZF@>;zxQapau%5CJ+RG*sqSP=QwcAk0_Qi zrbE!^ZP@L}bw8V1xnt}$@6d2R*57SLM$?!lhm;SV$9w2#{&gRVCQHLU-h+LtbMe{0 zyDIQq7+#FXStPLIHZN$HxgM0HtO*p%Y+pY)?VG zibdw_q?D&f)+t8R%?JVHL@8Zlvp{}l(}SwHr_?J?LDxVJpmcDEdN+0{n6r4n?a zUxOtZK(S5cY#0Fa*DCypx{z<2uLCM;&!&dOsf1tcx_<}GOw|t7$adN7r0p*T;~-|l z{%vZ`jsmQd(2sKckh}!YJKN&)2wuLng>U{bsf-~onJ$@=kAwf zHB2C`9@#0o2qq9-0>=7C@Q_gsD^=gu9lijcDGI<9&?0}? z>2JgrbhO|>*s{4I0f;9Tf{><1C)@!Yks<~thSY(Q^`l`yZr8@vZy&i0KwV~DxgydA zIxnz3oXrz9m@~0Ob_f8iM7`4z@}oZ$fAPaYQ~pPJPQf+FuI@c>Y_t@V?7|l?YD%cr z85Tz)wcNXLT-l%H;3+>t{3zTLIdG+8BZ~!(Y?we&GJAMXhT1Jds!pb#zWb>MIe_7K z11Qfl4!6O5V*-Tf8Hc-wgaq-;K3D=dBO`K$+wp3!E3xaZ_8VtqYeN@aCs?h>QYA2t zj>0ef#(7PeGM*Vuw-Au2SNhA>7L;2HIR5UG`d%~nEWk`$L;J-6IbAPEUFyy%KYnn3 z1sa#K_gCr?1pv}hA$I8d9Z2{~yBpIL{_wnL@lRy4JRA3M5EP z%RqjzBmd|sFv75iUDLcstNQ>1se%Gdu+C^jl%IVChHo+~cAo#c%68z~BuH09O(Ddu z(~0`wKLeg!AUt`Fi*G3>)UnIgb#bU<{V{TfbW_DJqY5^gXp{!>P>>s3=jH4f3%vf>xAuwB7#fPyy}y85;DuB=^z8Zea?6X6U!Kcvv_~D-Tp?A{71@2 z@9gxVgLjw{q!GQdEeQSc11OXfSI+PoUP|zM3ypuYLjTumy#A!W^eveiYI37L*MIFu z{@?BRCWH>7GW+-dwtU`hq_Rdc<8&!Vk(or0Hk-1SksR1a5K=$f27R>-Vxzym3!&&m zyZc7LdQu=IC(FjL!>IY1JK7OxDcJ)=n3Yff=>Z}f*xy5XhQGwzEIXfDaJ<({U}WG1 z(quFbnxhIzkKu3*K8sU24^QT6az5>(`}L}&%cJp9GPNFQ=p4qTa(mbLkeSuXX>F?h ziT`;eqNE|6eF%c2kIV`c(=Q8xY10kB2s^amcY{2DBOmvn-P6)|NL+jY$aSUEkhELL zCjdrXlhb_&&Adl(>u6cMULj=E4{7rnEa@w78Jo>8g7EvqiPBy5WCz)Sr4x#l7}3aS zJ9P=d491HK7csH;;E}76!#ll{25eolT_~sq&O_+w(Krk7z4G7VJ3u5rf)j8LWnnwR z<9w}&a=%AZZvJKs1zbx$kPh|~25y0E?Y!vtXg)jROlm+$yNBc$&d&{+m{A|Y& znM&chV4^wKvjy~xkn7g+Cos_LEkV z`7@~e-r#wxvr!3R-H+jX^jv`KmR@BVYIdN$c;wo@FgsV#%J(XYq*T>t?zn&rTi7Pyr(1#PSFVDliEMO+`iqdet zY(L_Jw)c=jh}f4_6AgtjXylNj;yMrNxXrC2Hz1)X{(O8G-39G-e&|_1cTQ1f;;%$_XW{HCmwQBBZZ!ZTiEB%P?_f4DxTm;rba49 zZ)86(f-8W#-r1ua4SV&s_v$r^3tU@8Z0u>TOhAg6YO07T=q~2){$l&i)cB@%i-J)J zQX+Q+Ci+Ov*^vq0MJc=72~^j%K&$+vu;|gi`){{)3^XgmTQyLuiy)d>#(ND^ZhMOk zs23nF?!C_sdQ%3Ysjt14(qkaNP@@pbHJt?K*$kxj+)sW4I0PN&&nmPD^AncRNh*R4 z)tlp>Vzn?VFY*Ca=fL7{!vZi6$uHob=AWt<%^co}u-K z)kWJ)F9}-XpYzS4blVIe(50H~KLWSyxbVS*@R1jy8>^ARK{b&(n+Eh3d_#pN2}3jP z3Y4T!qNRP0ZSJ{#$J2aAJ2>D9S0_oc+ZX6_ORWtK7haAY+Km@<*>JuL$0qj^EYi2( zzSdgVFH^vS4jkPbQ03ldanS*S=8xy}#vGic#TFlp~?rvQVLFG<3v zgC%_~J{0((XxI4SuIB^7?&Seef~sXR0UHIMJ0GgZeJD<=@jBoT-Sb9P5OrfCqLWn< zeGP>A2lx5Xz{YT^u$8bzqx6JxNN?JHKVn4*#o7x-r6Ys>;^>M*R8gAlPJ&yA_5BuXu1C9@M=3~PV_9js)A%#y z280r!xkpzL6*r{m3O=5Fi(7Vwa=H#af=F2r@<)4z=p?eokf{CMjQGza`#+PBEp#2j zOFN7xX1ig#zMizM;e2=2clc+c7~tHs0FQfn+n7lQGaGnZm=)L@vMKaff-3n63`w!B zm{132ze3G;6Bop10@yl~isroP<=E=%2{v`v%5d~@9By7I8 z$x6gOpSQE=0I~zg#U7^3D@cZk2y7X)eZnEE8tq5_4j|?Z!I>gzaCj2|7*t==?*|9F zPBP@%FMLze`3VNrT<$QAp|4%4;qKz7bHpQ9+aqH;H7OvArF*!P{|Jmn5z_E1dXOCuP5>KT z>&(NdgU7$Q%ml$J80VgXJY4f%^YCJeQ($coRfiVZEOE{Qs!!nD^~dCp_L~&r?rnM; zIxx0chc!%@1!ot1V@ZU_h!idNU0O|T50GRW5msLx1rc#0IOdLAU#mDg*fk;W`bJah zPZW%df;w0Nz50=aeFp_~zwd~Ay^MMZPjl;bnZAgWEI{Jj-oa@B3e$tV?R@y@Vk&MU z`;SmcwKW+Yz@SXne10uFd|o*wkMeI?pKF)lB({HE6b%$Cw$>fR83G7n1zmi-J^%nS zd#Cr5Q9FtYE>df&hr1q!7N$i5`U@n|u^~jj{~Rl793hNVUPa2C;5h*uL%kKo#pov>vsPvdwhbxRK_gIpLPo# zl#kZm$Dn!1LIkjIeX=E|Gojzsq(ryL$QzD&b49kXN_P_y*Ab1qnS%}pVJhwD5kJy1iog%ORub0$ zn@ZRj9}u5-xx0X{Zp>Y{0tp2I&nj5Dx!v6Uz0zNKMY4;B+2}+5B7>2}%3D^q%ZOe$P;5_X|E$rW{rQT?PGFRXAh7^AqO1MXY z5yYI~;Q_Xep&VSQK+5jBNa;i}F2T!vI;cp!QI9yQek=b*^F|^#=t^$+2X%&I&Ao9l zt1mYi>y|tYcXzB1jFf#^ZgpB}Vg(t-)%%6f=*;K_4?5mv9O+N3J+UN()Z6tJdT=+g zH1c`>)|!tsLJzTJDawb4b7Q0}rj-n_en9fW^NR_xaa`Bhz=l%xIO4w%1oTDnMghI3 zUma-6D@W?WtSZ5Tzc)zgAY@#WNKj$-aB^e;2unF(hpgKkM zcD9Atj|c5JtCwrs6@8ZTxheQL?GTMuI*--fS{4}$Sc?lM)o5P zc4v{#t#}TS_m&<`ZpO&VTv^2H%WW0?bepia5L1&fztK1!`{di>8UxF)m zp8D}carS<=2DKrsFHi;)CydUEqnke=Xe%CyIOpYuz~Ia{&^Jtf8oHt^{F6q1Z;*1> zI)LsC%I11e8UysF+T2Eb&0vP#q+K&t*5RyS}(&839_X9d4s{gb=Y(D z9Vwzz^h*zP>|kXtEUZ_<%6{ej8+^mM0NRkBpR4>Szfw`AIAH=Y`-s}QiGCg9aB=D2 zIwGRq2lqz~No!MU*;Pr(J01YERV>&M95d-|KDd%wzAC-Hh0M`ffoVpWwcApJQt`la z)*<4QH-TKnBffzVWg@2+?=yK|A#Z9yD|E=={z1PnEGZY$eQVQR$L2LQC!r!i4nEDp zQ+QiFxp9*fH$>!sc+53YSf@2H>=>??OqCf? zFm0$!4{0&`WqX>1=mLA>2b)e{x@t5%eV|J#NhgeBS=kb7rOztmiS;4)7#clKb+afM zYc=v1I%|l=Dac}WLMCOco5D{6ll5oEX$duqMsE4N_wiVgRoED9%0gNKk@1t*B{qRgw0tz>>3ye!V(68sjp;l%m@bX&reY_&q``9aVU zT}t4y3p>wm|7imSbPPN6XOlm1$i0oeRDLgx%X{OJPd^^k^F17Vr;Om+=p0cBNz5DP z9!dS71!z*KB6-RWV@?IK!Gs6QSQ3f8R=z zO}vfHxQSnZr7@kt5cCl<$Rc%1l@~X}lL#|D*R@Jpo}`Hlv)tniMF`wsG9zxA>J)!{SvU;k`UWse$|T7MsCjQ>cC{csubw*@2)*UNh4Ybe^B-&hs<*t!ogv zAJKgz>|D}x->&B(sPyl52Dz7d%IlYqtGB>Z5z|K_aPwXP#yb>70X0URFo!rFxXtVP z88(c`BlJjx%UiWV;~RE&W7zMQm;5-_$CZhtG3(>mwfwzSuwe5sFKGj8?s`0LEJ@G5 zf6-B3p5eXkr#3Di&`mDz@KrQ5hVX5)Y~{sVPK$vLcQO?+w3&phCj(y62&ysNPrM47 z%=7c}2hC8mlg?DiHtuqQlz||r{KIwi(k3tqBR*A;?Axq3 z;ms0U(|_Q{|J6y)d3VV0;q*`-F^c)mqxj1=l24J>Q~1_a4$HQE2Uf~GklRHDlQF~{ z)7-^g{xq+0g;kc1HhPBEp;r>wz9qLk+wn@+!{)JZCxzf>l?XS?)5B`olZ@@fCrmUb zg^LK0z~LTz)3$QY&GoWvB42HX%-B;JzH!MbV_V|SlOn^9;cn4YZMR@wWxD8v&rr3? zy@TOJN9bF(LgdZU7)(os$lwKE3^K`aB{(%i`qc3y$Vt5L0#1@jM z1swCBBHZokZ8GaeWgY_VHqlAv1svt1BU#S!d_tD2_V*>@`+u@z8n9$`-o(7hg4S6t zV9CzA^osw#wq&&@eOu`|1)CHA`dxhN53up^1@*8=EI86BB+{vFzmcA zy6xTM>FEr9aL-_h;HZNYv5EP&j~OOHG{&}f>agfZmlP|6vl>; zNzW0|3Yi8Z22=CO_r6!v=>-#`tJQ*{pEWQa>@Xj0O%O3yHbCE^#j(1*O7mep2I5A@ z|IKfo=QfZ3IotD@TX#>%6=Y2xbQ%f096cbMPl9oO7!>8H^tR735tRy<_y^GlvOs*v zf+j&}IOBTmf;tWOMqR1TRvey@Tr~9|eOCuFxWaQ$2Wmy;!z{p;y+KUY(0Y~m_M1?5(*22$VaAxL|AAW^yfB`*Y; z*YpsTDHq>ILR8+w{j|`DD-TiGHY;2kUE(pE?|&4PIUp)y-HZ4U3Q-w3>nggc*N>vI zI7DURz34djVK*!u&9}jJXE?1~!Fl7qx>G5*=u8RW?Jz(%DChZKqn;;LISCHDnVy04 z`?Ox3?=@Y@Gz? zgL-WdegGRoE=k&A=No^7`k|N83Z1z?{MLhHjd#jU52PB=W3iyKNJcylrqINFe$zz{ zA7#I{q=oK-I^w&QlR0f#nY7tg>B$&d!X;>;F<)5r|)QX2LYw^o+$s&&*X!y1P zk?(LT*{{R~9lL$Q-wk;92I`6WupcKnU9enAfc^eb@%X_0RGtJ7N zbh3sAZOR(o8aa@a{m0h44Ka-saWmtG+T7{RHh-Rg z-Y9Gwfz1#i(t)*APR#M(l?2?~=U^G1gzA+buNp#$(bk@^qGJv}{PQ$~b)nt|-E}MX z68N6BJv1P9#x3sC)(7LTWHcJ3lxdR zb}N%W_cRs7O)Rmh;Ert&FkV3}XjPl`L_q?28-8RS=p@+baXk&0>&apU^AE1DBI6!u z7-(U!=K;W*pwosQL&sI~jX1%;N2Bo2bKakcHPZB0y)NyC6PLO}(7EgQDF>`1^!Lp6 z5&!>i7<5zO?I*`omk~*hdguoe(WK?l*qKw-l0VCIV_=;|Jg94ggkT;@SFWb;%~nv5 zz6I;koX0=0q8ZqfRm=cMdOKAICbT$!Z6Ue$bZ<$`z3DwLes0+L4qcwyamPXt%d;kg zL~6Iu4EtVa&`x7CT3La%Bg(;hiTe%R7iOd>6r4_0`F~#Y$-yFEEnyh|%{zSA~VUU5yw?wfnU3}F07K7pJ`&DE@GXx*sZ`i`?Pw*iy zX}x4o^iY=xT8+-3c8;-1pa&2MSH1(9(I_m156G8ieQRSyl3q(dBm~ zF-rhVDw+qgHFB@7yzOwB#TRAuB6FS&V955Ipt|g?B#X&7=5q{vg*Gob0*n3Sv87Rb zDq=2wND4yXZ#Ag@d2Y-a2{0fqtRj|t;I~VqJw;5S z#0A&Z1f@S@4fpu$=MCLAL9ft^Ok-)Oih}tWugr%_V`lWfL_Y~Q`SnATh{8ehQ34>m zZ%aA^$~*17v`a3aTAhS4+s#A2`BrvI6d9$Xv|UcDgS!Te=k5OE~GXrpnz}5t>zRl?d;8sXs9O*-F+wmNW#E3cF+B-S> z9_!3+HY?S-Uv%^M`g`^^@a=pNKOW8=oIrNHmNPRj?f%U5$2@py^QdPrY?|8^oHj_3 zC9+65wnx24eh9vtVT9-T7}cd}{RrNIY_BUznf0=IscqK}HU`{DOax}LJ#TXx+e#Cv zJi~WC;mP#H<=aH(ty0fgx-!(aw?JCv3Q5P00C`ii5%CZV6gZI37{4|A}Eh zw5ZlL?ClTWTtuE?8*p4Snp>I37E3TEMoj~DHuSfN1M6f?iYJz0B_c_Im48Ly9UZJs z#r6P_XTg-IpiA^uk9$$*eey%M#xdhKrdV^ZVchnJDNI>qO74S!}Mpwj|`X(?oh- zfF5$gXB4E==cqZ2Ben;ie5Xyl*wrdEbW-PAiH!kNcS~caFQ7k1u?ABxMEaCu1Cw5&&6PA?Yi?7rvB)x>?mP5Kj#CD&6H?cL&lI?xY>VfG}*X5q}4o?klx1jlf*H_KZvL6 z6i;R7xWNwyu|kmNuVTEAl7g7Ne9)SQ@x%r`J~ggo<#_$Ae_)p}F*@}N=*$C$J#rU@FobZQ5-CP ztEQS(Xrp6>sez#+7p3bMhl|YOL?UjJi8-*~UHDR<-zt5HZ=sjaJ&6MA_3HiJcMn9l z%)aT~_KbFiy{(-LFwbjVM_vnUaPj1oE5b1$tR0o3__Bwb`i?9CEE#wCblzRk%2y1|EOioa?&}V+a;v+hUU+6`QkLjf;(Lljx15G>BMz$io8i z?zC7Ut>YNR{c<`k0mo%6r|(b6OzT_dS@0i3f+eujDYEOPkES60=z^J>x68LSgs(8_ z%Sm_;{E#A%^0OF)DBY<;MqiCtAez_>0scWs!w}RAy_s+ITVo+TIUFc+$UVcNaOwKB zoLvYTu}RD%cO)*%Exl0O;-6mK!~aC~#FVXW=vcmEK#FX_6Fc3UvV zzZpJJ*>`6W38mK_6@9!$sLh|`wh==wrvVO7I&X@En3QfLLyxG0W%Mc(#MnRkO!L&$ z*;zRYzx?@8!|`4Wdp)t5vftQspvRDw(fi0E5=wv#bfU>MF#CjK@q9w~Qp9a^vxe2Pwd%$l zKowNaC^?OF%Pl$yFgHMk${~Ea>K>K?IL^At6ReK&-_0E3&|AHLpOluC`Zg9eWEW;| zBogvG13oR3JcHbmAmkC%W&1W6;4I2D50gcDyS1T=^sY+#g`=qEq1}4Tl1@|DfS3SxgFPz^#6ScSTyqx6%m1 z{wwP9rm09jHN;{GptT&WkWcsbFBM#|i`GH*uAmmc%wL1}uuVg5I@0yj#End=nayp- zRy1TFH-(@#P}lvlrF6S!;H^+y-Y2A<%Qxycq42{ms&!nu1_1es(_@m|Cj%~6j8>|{ z0CFZCHmn;uII`&3?}2%Z4B?-HYlH{hND#%aJ@I~aqGkYaY+=1Zy6pLFm>=|I333NI zh$g`@gTE6vU)qVNdFTLQ*ozT8ID~ZK>)r^9Esd>{dkzL_oa>XseV*^WzMEhwzHgDC zR+veFg@H1F_{<(xXuqIVon|sjcarc4Z9q@|v(NnNQe?$O{dp-0zz%}Y{mxBZu{IDy zT|djBuTp8mdQ0t0s$E#QAKb{I#nS_V{Wc;Uj9!Hf_BnRE zt1B?G^#rC>i}U8|SoWG%{?1OU&O`={yXGof#q7(CpD6 zSk_O01$ay$wd-#AKXz3olLo-qDy%Ji_IVBmYOwdfeG{+!cFVJtVc7eR9lt+?2?3{b zd#GmUe(pfBbm|+)s}(t&-&iM+Ggw^#P64XU;knN@!}xG(@M!Pk1a^|7UNAo%IuBsA8RXB`ARtE~tT?n^9G^I} zD>!wOjrg(cL9vIVPhlA-xwjl9y>}_O%}ufHN$T?Pm)R1B!Z4pST1MQj&fj}p-@$_h zuapNteRIdJ0$FF?JDl;5IflD;Zave2heaR2v!lin?%&*b%(NhHN)hueC4M}HJH+Xo+! z?z!gqy zn4tD{H^YT^MF?m-ofc#?-Q?~Pid!fPD7NQ7LDGO#$W?JL zpE};i>fl{99Vu6heTkcWIqFxIg=dNYHF+g9 zXH_4nf}vdWA{kgRHExl*T~f@9NZ&#DZXD|XhZHDtvabQ! zs9o-?A!g$U$l1^$u^fC9{tSTpin&>OKQF88Z&jECk%)G@{v{s+4?RfZ zBM|O3T{Zz}&*l_91RCn4fw=Gn=);=e>VfYCsA@I6;D6f5u0QU#d5{rt{LuFl+mDU+ zFHeGIRk=a9`x$yx=6XrpvcWrpc?OA~rhJ{omqimLWbKEgGwa`ZA5kR!INiSp7dqDt zp(b9-HKq5_288aUi^FAo%WGA_55dl~mir-meqXx-NY}kRS~_|?KvIXB1+=i%mrZBX z0z1>!>**&w=K!2AmN_iF0`bQv(8cLZKtbzlU1U*sH`Z@n(F*O8VSvq=Y-rbiwvhdV z4|A%nE%JM;1RwdHpzJ13&c&@Vs2UjWZ6d@P02)bPI|bFL?E9#A%Ao_;ZwY5}5c&{i z=G<+{+Fr-!4t;A_0$43cmxPN@VLyF{`I6w)1v|=G(`hiRU;q$iW#-7TH28BYl@1zMpJDxp|>b}KV{&S^#HZw~X z(z%>3A;+lltW=2oL}PhEruxc166skB3^%8_R9ah{e)u1;L#i|q1%UCtf~0)UPAq+9 zb+N<#LkkdwdvyrFk!rbP{$prF_KWTWpL5-SS6ATDLVxHeHZ` z_0a~eC3s@J+%;-+ zyJ`-SW^Uj7JryW+;XlFYN4P4iC|^ZT7Ca1*!f~6Km{f282(CVA1d%|^QgG&Er&FG{ zCKaZQte;jgu#sHe-h2Za3xj%4Qb%$(zO-r;q%iYG0b_Dv!r*GHWSj|9_+P3VRrsf| zhBmr&KQxuh`ahz*WZ!0zc!aq{P6wSuy3G~1&ITSZsI)9_OOD2c&oct!gX_k zP%(Aa(=Kszej~usc%karmcQWV;E0^_1yP_T=IMJ|5%G!p-wQk~(zNTJTPeN{o{nPQ zlI$R2_NbR(tzax3aW=W@9H64M8*W_op5V?{Av7S=Xra5?K{0U3`-|#vOz^9%UGBN$ zV=)18Dx13C15r8+3e00weJ9sGw3`i=+WBc!niDUG?Shu>bffck|5_fX5jhnZ(eY)z zD0!nvEK|6c)F@O2`88vC-`-;vVDR0)HGf2+WQYY8kFY=g_${1-iDKHN@rV-K1niZ{ zdmgk~;6)=Wc!B5AnR7%H0jmhwQmdkm}dy}Zw7m*e{CiNMkT2zcGduvk(NbOEL zPi00h>;Fjl+$0ui^Rfkk>k0qUVW-#YF#LgZ{kA!#eVjz>(LN>D9p3YD%a2|q!MUFH zKlc^XA-Wg5Yt9GDwaMG7lmaPCL7a2{RR14TJ9s1N+*aQ^gV_;MC zQsJ0jt0$W1e8kf5&4*^$`xBeiP%kRFGeP_W;`k)EHnY{4F6ZhthfP(pF{julk`sns zQ~TJ&^OMKpz_szX9yuQM2No#M4-n2_uyqHH8h+f&E*@e3It<1R2wy8~XbO<<#ElJT zav{eympETo`3~!>IgY@Wc#(2_O23#Wih~UnATf-+L+GPIj#c zxeDOSr@y%A26>q{n;;62m`jCTxRVN8X_2bkljArzNbM0UxL=n7>rW50^5T2eKc^o! zfy@{`l!^6Z=hXnZX?HcjQJ9Njh@%S`|JSdg0WxF(RSxF2(QtgCfI23}popwCDABGz zg~5QDQboG``w#%vkIart--g*vkcl3tyGaZ^|Axs zGkKMpRZ)1IY+3zIrHzgEN8wP*z@c{i>ri*;`oN(Mex!*U>ZGGXeGKchvqqK84Oh$- zGK^cgSl{6;3{gRe^3&T-Gc^T{slj7k7RZym@by*%j855W>|aVX{Vfqo{#1mNKs zYAJvku~A2|mnzoA$GLGdSm%4&&UDp>UTb)?+5RM{v{a)UCm~n0KpbIVUIlM>mJQma zuvZEDh#Xf!Jy)EjHeyS&_p5S6973e~qFShXT+d2QsNx{`_#_)tOTQDE6W>PTF!^rY zZ;9`vyH#oNbrf5Bm=EecUMh|kL@DDLs&;T)_DCsu6J;^1@_VcK@7VR$nE6_sJHAwz zC#&e^S<|QClD*A6a(vuP>q70f47eHfN#=GqBrDC`#y2;PDA&m9mH%2^^MBTAOmchO zJ7`8*-~etC9-sEdA8P4GSkw3o*m#yQlxu>723iHL$i5%bJmGTpi)bLt zts4|j#1JQuO!pvp%?+J3@0v0E>)-ol$J5a}`du&eCTIk*9ylk@bA&%H?E+j^cbe0l zQaY+hnZ0~f^@0s}qkL`@XY|;4)xDNw6!{S=CPXjGt;RLAO zc`2{ww^y3yRVdK?f-abt>+7BDkhbfVDJ`SFQykjQYhC!aq@OI?sJi(mp)yR_5mfQYdWk4xo0eR$6%hedx`AF)6pPE;=8#?kF<4Ry$Nyx#qa-!Xeka(pdq3d{`P^bZi5I{1$3{ zR_$MiUtfJx^gdfy^rAf}U$J*7cqY88vbAp?#o0IsV`&y3sps_V${rse&Zr}&OCADO zD`K*bU#`DPQ0%8N`;g}2aZGP+cLDlsFM?~#i<#RY3%9G6l@p)|&nmj=-0hKfL6ACtP?A*JEsu1R z+Siv-V|Ye_}KfoC8rqv&xTT%4O1gFjQcshJrKg#M0;uxHr#4Q!L zpRk7#gUcF{?#nQ*mHWk0Hd#nk&Swn(wT^ERaN@gMG%{qJ61#}b{P>eZSk!@6C#ruQ zl?O}EG;6!y&~*p85Va2yB!{Lzo=Hmm{GlJg=QwVQSI|wdd+SViFCJ?EX5e}L-Y@7M z-)h(2;%)YypIYlOL{2?lIJnia2KprP3}LS@^VjY_4_xNqhc0{?5=0W8Q*wY7f_(Rg>Zf&N%c5Wje&r6 z9S(BQf3YRj5UCQoNVqgN!2cQY^w#oa&YJXe-}Eqyq^9C$t8|n(yX+uMz6Rv( zy)+hvV6svAF0YHxETRbz@i?(2>}p+<0#I`orkb!1-+Zu}>y-j{+KSr!Il}-Ne_#6U zG<;WV0!qd=;(_eK1v_3VcS;+XjXE&&zCaM5W4wB#+875(Z$h&;@HA4zl@I3Qxv`FR zJ-64pM6z!mjTk`K!uefvOpPewsUR8IV@t1XL04>IJ9~Bk^acQoJ<9fBgzAzae6V)z znpzXg^ZL>bT3fy95dytikrPa|?f`BwpvHYSF9#UP&j7>Ekp7&50!0Xw`6v3EL5-(v z`35*HeG&JMX%9h4AAvMJMBtvb5X4DLDyu2wCe(TAuBBPXjw4$`yS!XW6qi&@>#e0% z|AH(yqpef|uQCxp6kb>DNg^84h`0FKvoP)O*%~L-F!T&Gh*E59J|UuVRc{&dfUqlb zw-kD=T8XLsPh7Umne`1m<%Jnhy{Y(G?Aa&!kEcv-KR&Zx?RZ%BP!4SGfyx+9fC|-wThgfyFYM0so^u|fnTsWV zI`-20z#P{^D*mg8Q|Y~Uo(GmEMT^0N39t8NOP;+OwETZTft|a$(a@*-1_=1sc>hohcrzTs=ms%VeSrK)W#_z$dM^MFauQzAvWe290xf!e{^!=b65P1> zJxa0u_0}KmYuGV~{**qc%^yH2j+?q$d{zCR87VIj9}`stsl$mlrwU3!T}VhTGi?K) zdSb5c28mnXaiNy?H9vZtmj|&@&ZYl^0&8KCq`}!@)sKdu`Sfbs0qfJ+oFHWi;?GM< z_W%tv;^TQ!xa-cVAX;G>2JMND+iH4#{Us!nH3r0YGy$@!+F!Vl3_cfMRnljuQu*y> zCogF&JdGpM=&f~uLt>-uphdb4NwxtO#%qN!B+U<=ppZF>ePBs6UNr^##Eri3(=A)v zxXJ9>_bX3nYyZcn@sU_E%7guUf)faIv@zF8w=X?33Rmnuc0rMtT7r!y>sCLt5RV>I zl7n$y<2*2lz`ntuM|`CWqLW#i;aLh1Icc2Ctb8#i;)AaGj_<;@r*aGl_;5$R1Deq9 zHba|;Z4(!F9oM`~cm7}s)Y0kQJr6!TI!pPqqoh!FSp%%1UIr^Iacs@#7*r4&rL2ePL9Wn2H`NGtWCX~h2MNz%eF0|9(A8j=DAhnD`I@>X zmG+CBgP`OVD^kA3$}niT?J=d?SVu;C7PWur{QoIz-H#}Ac4 zoy3?2^?u`*lfp!M9O?1a&&?hX?^OaT*1AMT^Gwjc^j0+K#4(RZcC1{WNMq^cJ?hUY z*QJh@I~6Eot8q>hBo98Va3@w!K3V+*o^NA7SJu*)j|iKvn@9YrjMzon189Guz&H_k zYm)kaC#$J^XM}}>=!x9pq?UP5Y#o4#YG31aWtYSa&23{kQbg$^k_d`+>cy%@5RXRq zfJipGQm^3<;6E|%9lkJB@(@af*h^5S#sCfidpMoTaf?o7fnG4Ze4B|5Yp7^Dy%6A5h=x8KDfti)f;sm z%X)uPcvz=-n`O98mGMBIr2u&lQHHLrL&3dCYGNy`W7z2Y&Y|4a zn41!ailxyM$}S)1C1?69JuN}3?OyOS76O52{pT7fIyA1>%A;4;4|U7z7qqA5K`FnL zxSL(`6+E7bMIug|?2HeW<-0sZxxKg6u0tr1!J2<-6XXgnPJhd%k;W?(5GG^gqSLX- zXD<`AEcL|ix@DRM)t#aql{)3GKwrvuurKxgrsHeu0sOWN7Io2Ei1h4pworoT9K zk%UizhWcfeMuqd;&gSOHuf z_ZDRJ2~0D@z9{Sv3bv+XTL9TZZmA`YE{2$$*3R-Dy&0*$7Z8))Qa~HCA5I z!|^*w>zBZ3)FlS`Qf>FAHg&`gKNf%)AYS6FmB&Ho2Ef-b&E71H_omCCSjY}sOo7I* z@RL4$lRQq=+h`b}B^n=+%JOJ*&c7X?I&+2d`l=g4l|P5|q~uN74d-F-$q4!(;!5iB zxkHnj<0a4@XZ|n7z5*)Cv};$%mwo|}6nF_iQb6erK}AJrBqf!U?gmkj5K%f6g9a6( zyGv9WNeKbzuCpI!=KE*n|IRvRE!He&xe$Hc=ic|;*S_Kfqx=w>zkmCxNw>>bm5(`) zfGs><`*_QhEC7W30$AmVn?I$Q%e#=Z(b*dt_y)pbrA06nZY!dwt1InCLidT?p|HnQ~45b+4=F-+96Bf-~FHs8;J~qYB4-25( zHpz;Y^7>e#oJD-nll+Z#yaC!U=RWYu?DLXymI#CFi~8lNW~V?!qkZDPo_djQA>4|> z1;)WKMSxyJM<|`u`1O&ThjJ)Rl|F}@Hs)M?0{&xPo+F`FN0)?|K1HlOQVSuDY+IV2@a?N zqxI?THY43^0rfVg#I)54&oM2Rj!&(aqHzzQ^5M|G&iM#aW*j!DVY?K-bLOyQyOcJe zD#UoN<2|{FmkP5N3Ml}j$84nINr(vw_DQ)oAb{-AF_c%*iH5!MC1UoB2+}E=IDZ>p z{fuHdnUZzcd5jiEsBdU+0;%!>mF zu#)vAUx^VN1h&(iu7{apfTQGFUgyGia}7+r>bSo1S?`TYYWkNr`EdIEwxk@`oHV5f zNf4D8tdQk`5YkBo^F89wY~^^)i!75uy#J7Spg!Vo`TBO%Pc|&vpL?=%GQrX&fJgyb zupNqykCBY8&`a3z4@+kug1G>1cI42UhO}5%%f zi8#7Wh480Ovra~&iAZ^FoNq@3{lBJtv;Cc{G6&6-iGgIGE&!o{r*}21%(3K&@y!zc z!9C#a@qFrNd$F{xY4O?2T7|h1Iqfe*terRtJk~8(dq`NASoDm62sSEYZuLmtpjs&; zaa~hmS{F3Af4|LS^T-P*A87hC%rK%-783O{=KO>oCvh9X!kJu? ztJnl0^@;%_4%yHC{ACHFGQkx!(=wG}Mv4|*teXeVU)9Sze3G>i$!+}yqpf~>T(02< z+;!ApiQGYNIzgaqaj*+?z!qq8&ocRJ+*BPzHwP4m;DE8DA4c5 zj{vlXG7Nj-e6pQrPl&?$z~iEO-?*kyV=$c{;C$&YQFrMn5F0zuqD4nqQ);LCZmQs8 z*cP@yK5`m0HJA9YvO-}8ru0{^OAwffo*UO^aW!b-VZ^}cZcwi%86 z#*ON#D6~HKTO|ii%Dw#77U=uKKpcJ~^rh%-atBm`yj2yZL-J{%*GDpI6LQ{LcAdzUQas{w5G1 z$siqLu60(PyXcEOLG4n1%1T%tUW^<4lN4=OM)jhp)AB0-kW>1GYBMv#;d*oW%Nz$8 zXpNv9oZzMLTLg*~DQWq6=NtOCy(l!zDkD3M=h77+q#D)p%TaIqsahdCpON-9z zxEw|Awh9S zlR7hiD$}sifZkO_F5Z`fWo>W$0HoN{dN_eS^Ir7@vjqF^mx%XgU1TDz1WAb^2!4`t z@8yrXfp*k^BTIwD&2NajddJ&UnljKZY6$uGjr>V+bh53ws535eynEvl7=1tS)=GC8 zF@jXj`@BNmaOP9;Z`ty%|8DTB&`5)?a(k0yRr-eC>jzRT?)dLPpeT2OcGMx1)RKHt zw$e41WY35GXU`aZ8LrYNXe)`&u+n_rX0{5C!R+uhG$FME!8xF-lZwr3E6FQ{;gSt4 zq8;jXYcGLvIRnuyevw?*Z$z+0;CGp35&Zugb3AtEJiL<+<{jSf8FkvS>U?xe@A3E; zp>I=O>{HeDu&>+iRCfGTU0 z3WL^%O<0gcK{+K9E`2%dC^xMyu+aPJv2pPSBlXD~uGB9x9wm;Lk3GefH%iQ+{eUPK zO?HxA7E4!|`O#RB^A3mg*Ei&>R+YxJSa%^g>DCl*c9<0AZqo*T1hSp|(GzUDI6qkB zJ}vM@eK1yE1JO^ov#b2ddv9qV<YVVPrT~a#j`buWG{#JsP zZ%x2H`gPd{rsadPpa5XA{`M{w48+DduuN=4R`Fl2jRwDu=BM&2{$L0#`d9f`-V=zL8?=1ghc$?s0?c!%)K5pfvM&*4{6G_yco{Z-KZD{c zr|p$RG5DU4Aj(Kml%Iv=@MH2VMj?xEkWfn5bv_3-`p#4N&M>tbR)IQapinhCm6;Z{ zh}VF&yf|c6f&-s3HEL*>Hkh3s%mYP@>Y!xvtb`2c(k%5ZQ=(>|>l(~0}y+vrj_&4ixCJAz&*qgO+QGk;vPAYg@y_Tx__ zz;Fx;N`~*j?MmtWdbvcIF2^UGJ#NlN8yU9qkEDC(S_Xz(4dzC8Rf-rOeOdrtzwAU7pMzyvR7OKBI?H%-kPeOQbsgmMyD9YzBa%3 z2lF=2hRJB5jtXD-KIhj~2T@lLu7yr518?!PqQckC?m)iY0HU{!iaP<6r~2O9WJT!R z^6$=7Hr5F|vC4c%OddW?qqPNFdN$R_pyob>y7?tdZ(BS1-3&XUcbk^`-m&+N<5O{p z;z5RH!&o_DU4Bu154OR!Ht24x_HUcFHKuBJxwzJHev9h{=hT7QhcNa3v(V8~`t^sS2CnppfuG+7S#z z^poF!Jt8Q4rXG!B4%)Gg{nvVaczhDQuS5J`8JNxHjCl%G>Nlh`5e{*27;t*%65l2( z+Uk9@IS1RK3%(_}_^;KZNq6U6ftD6?Mi@Oz+g3M(cT)8_5PwVA%B(1Yzhd6}G83)1 zc<(Fcy7dMSk9`pF1b_^ z*$IwvA{9b%Z1I7kY_bNW7>+U#9Oa|K8b;(OpFq7__c%GkhB))LjZ-GyH-rWqp#>v7 zProO#YsBFh*ONuJcsZhbkZQ{B?y9{7#4|cuSD{g|$+ON8QrEUe-BL}Xfnj25pSbBU zoJ;092A#I}^(ggM^;NH+jU(I|oez~VSm7Sj?OED`?c^&^YguTba< zf-NJ`Fd0j7MqC3{J<~%+OnwKSPpv54nY(o@?tE>FB}`_%mlm6ehI_?~LPn{d-#{3$ zpAUYa?t3DS+pMrxxA!Jk|ZoLJVfw>8jrEYx#jJHRgBP9XH<`AYn?AT71sjO zS-4E@(nA9nLe`HpTjpN0On`HS4g z$o#R8Y${=(5PxRI;7n{g7`_Z@X~V{(@u#Ve)=;YiJ>?9RpS>(=dK!BC_vn_b%}aXB zg-_p;w_HqLe;?2u^9W>3t+K_5NKf>)uXHdcAkC&z2fn7l%E7PlWzt-qPiGzTWPM<< z6Utc!+8*!XVPKvbQ^pw8m%le__;%Pw>Ff!F)&$OW)_@Uqbfdj4-#$kqlZ+i|MD`_q zoh)!G1r-AbPxHimRXXMDVMrgKa1E}>SSZQY;80R?P(;>SA;d%2y3yACg5jDf=RRV8 z(flOT&LvazS%_!#;nwX;2AoHr&kG^^WFEQUC1U6~h0r?@Vl;B81n4_Bp!Y@Vh-H`i zs=VpCp?K?qiv^Qgr9Lnzi-0WiEHBeK@6#plRv9f71o6{3(KlEA1q1xiPqYwYeVR!H zR!$)zL_4b=W_QtMphsl!?w2eAoK$0U-X-G_8P)T<(z&S&O#6wpNdx6#x71&V= z&NS4lDJ0eRilmjYqksea0axAzedr6rd55dKhAs3+Rl~NO^k$2$iv^$W0_X;~X`dxJmop*FbvZ~+}( z?~=G|&Bn&!R@1W*KRYQh}aTmZDG>85GoW#&Vij2+%gKf%MP4mx1dDkk+s$Kktt z&ePBtJR>OUA-XXPcb3K@3&VeQ%YSr$0sg4z1V4&E#Y(Sj;ra1H)`sT?34E9QE6^`{ z8=oJb=$Wg1cbMu1U?FlL(RqKVq#b5qt0SM4ak+EAupT>`IFC=p$b+nBk3(tX00gFd z{=oP#bT&d*zB96bEAYJh0g=2Z`cOVfWz->sLVl~QS4#SAxGmZPCLqE<0)(R8 zA_G^8(b~;veFI!On}dV1vtzN1jK8O!ElQQy^+3&WdjRNfeYmHo9fo+DAeN$MM}fij z#PU8r-h}lIC7N>qNkt=ihAYqjCIO|Vf4oLINK}s2OBM3sDlnO91;Yc%zGEO+H-&jx z2KI>2>^BCsd8{+N&@??M>J;ck%1@PYZ1Zp*0#MaeV#LY9d4EcxrhQ%3Fo<} z1@@o|9I4(#@uaoUAm{8X^@e%0bo|VVqGSm-MAeTV6e2QCgq)Ok;6^PFz>A2meN6s~ zdZx(7J4UxuFW^Spc{$IGITK&viEw~=OC#Dr-eHE4fkY1KHN@oQ4r$o9Hh9JKSM=cw zRBQl&f|gX)hA@bgnpEzf-h$)x{i1BnSl2Vs-a5Wu3X-W~qrweP=IXJ-EhZ_?uQ^@T zOrKA0bm|%QENzjDoK*k{ihH{MYW<9WXkXztK8GYRCNTOx4m1Tj%JB~3(9krNl74j; zjs5X4`o@p?`ozsz+$COcQ0TX~^ptOSHd`gF5ZtxUFiVc&v&4_tm+y`&iO})K6x!Vk zRq^hd^YFBex%)!hKE_~qT!hFcBpJx$w?G3GEW5ek@N$BQ*qS!yE~0ZqL-8B z(vS6B@sYeiK5C@kdcD2WQNV35Obz$58@WlEacL?*Q1Iqm~s)@4|$ z*gURdJOFmh*|Tb*SjIO%)v1~aTVXn9Sz4rYpokvB+M|UPN{8VZ444LZcHx|^Das6JnGdOt)#g;Z7R7Qsil2)0Y#Ui4yO+##$q( z*cXc#mkM;m)g*1Z#^xNc?2>zW2Nz94GFzM6L8r>29^93Ca=9SCX-QRLkMOg@j@fvk zT$I?tC!c3dFk{hKw#H%x&fuKg9Jkm(th*oApLQ-E*RFBNAf6q&U-i8;%_|eXTfIN~ z;t9sR%>9o!KWbkDN?mSyN-nN!IO-STMMgaO-VywqSs>;mN>}6;Emcp1{H({edM)Yi zN+^#tuXkf+;XGS5^<9zzEMFZXOk6l44sI$w;JoI09!xaPdUg%mM%hta88$#%5?D@N z%u5H;)^?!M-SrLGc0(v=S0DLIf&2UOT`{aaS^Hal%ec1J4&#;CNrUXGzLUM(0-*IB zj*x^5vd;l5=Wk{uu*DS_49O-PEpu?B6!-g+>Mua5P$`MJRbQLMzmtSVn~^L^6V{ma z%m3>a(@ex`4)KW~$d6?n()?3>8Sa_)X@(BqO1JRiWgvg6rbrIvjGaq}{$7JP2Y#lB zg`NA&ajW-w=q1w0vsW(7F|cgYB!&=CHz|pK8xFQg9)EBwPRp2uG=C1#60~`_wL$pp z2&#tIuw9+`7)?gYY=+Pt<0ahk;+e=3rH>Ds5F@5QSz0|>*eHkKh2qA@o<@%!E=a1p zJs{N+(a3xbid_|8?AvmWKwnJ$Oi4tMry+FrykDxi!7^gfiIAq~uWeAY(z8suzaf`SNMxjZr>(FHh5@k36r8lXESeFtDm zN-NH6NMaejx|l;eIS%AM0Y$o8%jWb4U%(})IpZ&B?r!B8363TyA@D$zR|;RJc(&PK zUvPGov#wkfbuxs_DveLFD!F*cmg%3&@!DL)`&W*i-=U;i}wrx3wyZ39-pvrkwq@NUn$bjG% z*P&wIifgs47^0W)msIU7VKERct8~x6+J}rvc+9(!>DH3SY_00#9pYg#tY{y|Y~X=R8fKmK=u$ zaBA0~z3a1K%-}2|2+?CA2GjW`aU^bXK$mwfeYMK%EDh=a*0Q%u*py*~f7n(vR>ru$ zyC}tCCXXKFa{UD*fPP{;4Pj|@vS1t|Iv=~owK=zM?Q(tDE%ISQp9PE6Rp+3+Gr^Rk z)%_!{Pe7&aLbH z<(nZg!g#=nleU=h$?*Dt_%QygSOn8xd@AQrlh|g3 zC%^5^qW&5c_Vekt{Beq&oY;DoV^A)fH3(>_kYYt$0Z`mk&<)xpiis2TR@m(zOZx}H z{`-FmIHDdOwE{)+GIaZ<;^EBg=x0q;h8)9SVl@JdW|J=cDD$+>p!thN?-}S9XekMj zO_@Z3tDNtMA0;Zvb5A;PUukA2c)*!(5FjdlU zUHQGyoVv|2f1F+)Fh0k#l&CLF0ep5wyH9G0(@oT~C<>GAo%eE!4R0x*5C>Z7c4NAM2gK}8ax4pd zbmt;u_T;U0%$+JFk^7EbycXZvPVkQ5*}_?8yUp4(d$i71+<}{<-c;y2CHKCWJ?i;1 zy1ouV4xHNV#g4s$$OWwZQX(oYv$IAZ&&Cku5!+Pl{p3EUotLyq{@%-(gmp19;Kn6> z);U+*pi#NR6Nm51n}8W&wv+Af-4O9wd8t!)r^#JD$*2U8&}x;>d{$<7A~{j>|!mck9O!ySEj&d}h642`f9g+d8DXLkBp`>r^nUh{OrCE8BuyA@M7$%%gUUJZYIZvcX>|SNmX3@A!NLnc09-~=# zQ0YVPU#!1;8yfn!d!LmR`2%UVlRl^fk@#!XTa*FxlE5^jbP3TWH_>^o)&v%fZ#?L! zsJclWkLoRlF z6+~OSd#3QVAA)QO@J|$w(^sp9y`k^LhV7-Mu|M*3Xrp7oTscDP?Rt^gRyJw+y~0 zW2W~BjA3mR2N?f(dIN0O(V?O_cLFS(cEn+hi$>p&ew4(`jFR?(#SxC@E@A@N2YwPr z#827}3waX|rxIk=2uI1zkcUl{gdeqbl#>pyP_&q1xn@jVOSljX96X7yc2f{6Qag7# ztJL#NvdP5%bR++P6{8ez9KXqkfm6#okb6f|5FJ|Y2W#yqeVVSFaRzk5ON`n@B>=Ej{pobFd60{aGM13x>0;6nK;30^ciiWuP7NJ(^@x8dJSE4@@@%zep++$wm)7HMte5fU!V}2dIK0($3K>Yu*`O0 z|7wc3Gb`p=Tp$6D%;N@9Yx*qRl0=%E%8ZJoEq|UYVg^>(C36yb;R`93U21JnqEkG@ zd>KzRP~)zE#?tG8=H%KOgG;TS0QgYQ5JR$_K#CJNC$VoR%sx#$p9}S}P|2AF&>TmS z-+5qv!WT38B5-{j`pSFZHIi!}0o4La4_j9xC0j^QWRuxNtD4xG0N8nQ9iQA54TAYq zN|=X39CPcY(07%4#H~(q23Y|JGzNRMQo9Lzb-d%?JQM6xLU*}o#yd+ZpK2masKk_g z!ITe3LZ9EL>w>cruH`Y&X&%lM?Q;e=J6CFM(GEqP@I^n;4qZ5XBw6XRL%PlV>$dqR z@ofn&0jb}BhcHrgoDz*EzrT7%y$iTHs*k?RBsctGmb5Drp7q~ghtmdm;FNpn+hKTX zajeVet37q`kTu8=j0d0ysJ2F%}YSrF=y^; z2yKU{;{R$p0M-lBl9y$m?;hU)hqSo=C>~S^E!l*5YIk1^2jUWicdG17E{+_u<$g$lTMgQeC8-71Aeyu1b)R^O=b{JEOSsRW^;t2b7~XACowQY0RIJx`4`(dl3y> znI1_UMglWRXk7|3sWz6G`{;H^dPo9zz7B!#15Mo1Nf&)S+(k)kAsL!D&11~RftrDc~9sU)xvW5aAk_s z;pu7d`rI?0)}(%$v26W|V$XaJ%j8l0yQ*a1LOX&7ShLiNseSL$V%pw0%PzMn&lY%` zIcend&Haslw(|2smVjx5N_*+pY;CGB2@c5-!N8gGjp+l3^R1tWwBtbKZH3#5I1S#X z+F$;cvLlOi4r`x?vwtf=$2Uk(?VDvuyc9^odsJ0@)-?ihopsVQ^fdexZrboD)1%&vikxWPM8st=X+I%OZD7`Z-I7TL_Fa)vO# zK1*)-Fs9}&EkT&9v7>?doSL>(wfx~f`OYh)pyyqmdE(HwQ|DlQYtEG=Ns~Z z?omj_=%Y>i1=yI9o;zR$f#DNwqZ?Kn&c8Gm*W_9ltc?bsNg(ly`?f;cL@>1uEl;D5 zJqOE;bf*%DXJfxn4_|VXW>3NDu%;~pH|6G^6JSY;utPn^T4Edx^d1BEMB^wR6bs}7 z?O#^=ib&3fl`it--2%J!(jw!Bl<%|9JZGaZX~cn>bA5kW8!VYMg_O`LldeX{^iTLC6hfd?U3Ri6n-#dtOrJGj5&- zR+em5#%n`YN{W!K&x)HT94R0kRN^J#p9HELo^!2wSl>Qxxc31IqY*aE?FBiLIP&vt zaXqCN@Nh(GUHeL<4DFQBS(Zh?wSFq$L#}D^qGE5Fh zr6D-)(laabxCQbbG>Lf#LuP&7WP1|EP}~pS<@^waQUaRa%B9$Dpuf8Q@is6G6PZk| zl|heh`lU<|SXxb10Fm4R+0iq*Pl^jA%>A_IE($&t1ciP^?HzT*2IOyFf(f$X4JK44 zDyZpTHRQa|*SQnMGJ9Np;ZK3qwOu_qX8nr&R+p*tCVeK8tw@{$q($kttWWbG#R{nf z=nDZLGi$a{Y?ea=6IX$r8I;GBRe{X6$Zk>0bK}jf*w&>=5T~T~0dN0rKmI>|6$;9h zO)yQC{|V&O5xHYUjRG1#exf1wrzo56)On+C5h>s>oT(stMndc4=kx4UKSB`CBXbaSg^pL+*;Qx^r8PBQy$!`-0gA)o zAH@N5F6~epmQ8;)J-svgU00ADxl+vp0}4{9R~W!cX{Z_z+4e`T364@n-+)B*C?@(2#Tt5asb) zNq2gGvaa7fuY^d^vY&s46v;fxq!a=z@8@moy4gUdWp@22*wOyiHvKqhU6$Osd(-4w&L``J!0Q8zvbu( zT~h9~%cYv$7nWJZ4mu@{FK=aW=gDEW-_e&ZKY}*T^hvqWh4x`Fr$xA_mQ_Nn6WF_a zJr=LEU%fewGvfCl=ls7Na_ge=3;p^0>RGA+EZlla;IKf!!x+0c|3x_kCaqvi-Q+Nb z3b!5{U1cUuxbw(z+;y)o7GAEi>3rl4&;gelEz|D;t8v~G|i3rPl zVB?L%C^#QdPd)1=!jlMVGF`{u?2?Ck0FP`?*Y9B7^+M~$hb)2P@ghva3dM18P=V$; z=4SieRHvz*U&R`f_1|7TzH}~UOb+#!H6w-b3~e<(N+a;eZre&7sWoFZ?r2_GpM;eq z2r-ObDqFw5Bv4YmoO5#koECTorn5S@ob5*6q%2k@{cZ$U2e-^`OTXUb8s4kj`c3!v z2ZAv`*-}#PgHOs3g%}mXKPh&DJ=*@k&Iswnvm{Sxkpsz$3wCffPBIq5cZ*V#LJ~8{ zO*V75x-SN9WYvR(`B{`;4)Jn10%-QXE6mRZc0eA;1&o2()Kose8P0HYHJL#$QsLA# z_QJo=3{-@y^x}4?T5pbSF>$TEL?N3JF@&hk_fJt!E)y* zAgHvL3%0>UL1#VJpuEotU$J9}xXyTh|2xbwH}2rRA<7uO?(?AM6i%jE-nWf)GxX0U zX)BKUcy9K#&)6fiNFMp)y_Ha<%~M5I)^B3Rdg zrl2>sS*UmMzmM>~Y{sgjW^yt{Q#PP(w?ht!aoEp=&7UEeJQWL z4|cgusPe#8i5VYct|qM|K5BBrZ|T2y4|7oAs6YSRDa2o5UYreitNxl0tDU?2Pa?o& zQ-8Zo*f%Zh5bq%tT)xDw$IVwv^j!zdL*1Q0lEyF`eNW8YRmq?Y%$5k|`}0#xebCCm zGT2p55H|k&IDUZt#T^qFh6{I}{kHW155;x+FtO)cnr#R4hy7x@+#mM{kY+=2>h(SJ zB=oCR;WU+oXWu)IU%d;hQTW&t_~1E$*s)pT7=#vW;2JbNz;?dLmo6-l+Y8)Xr@=ZU zqCLi@e31+Bdo=^^(lzisZvLA4x-o^3BQ^L6elk`#Le8Ihhq&#NRO4^lcaB!$$YtCy zv4bqSsUa{aG3r!+I9yVOz{HAn+ioTK+N@>yRW zm~?BKd`SH9n{}A?+shh-M9ekqlbR#uK^vW&pKA^yFHmHY3DSg^Bu&?1@?sf~~UJ zm>sL2Uk(@Bn0Ff~@&RRmQ5HvlK1Uu=(Pd>-H=F93&Ix&D}kufG)dk4-JE%_s|)f6|ZFuy=vS~T`R+RHrH;X zuiKmv`v@r>|Gcb62&NY%q;X_yfwCjPoOo)*vS!v~)FxHKME6KsQ}Z?T66GBh>M<}3)ARH~33S9-T#oJ84N6oe@MQM14e zguJEX@Vm_6MhtTlZP&s0800isCDqneLyKVfbwIX&V84OmCUc z18Kv!)szQHs1ZCPe5{_#eBXl`oY+(Qh=U2T6kqsy!#9~oaR5$^bpT58Ku9#k2 z&ghC}8k3wW&p1tXy2iI1iOgskblB7{<#lPd78w5quUgnZX)bj4LZIzcpvDH;CU5tC z^^*P|+?asBEHZi_x}rZyzfFH4QGYqJwGoMjnSU2Ml_-AqpGMulUjPOX3Ec(sfjV9@ zgK#R+S5<)v8%-bRJuYU5mnlWg|LLi2VR(?M?Dw8tHq3-YFG^5~g`EX8&;MXTwJ-CU zXC*US*FRk-#UD7l`_*6#mYW@kBK%pceMg5N)4v4<4m7@UYa4Ctyaqt;@Qio(X&OYw z@M>V?!JG=Zu;fzGd%|SXfQV~Rt{Lyc?h!VLjuVhmh#FDGOZ&Rc4}G-dhDD&WLUiF6 zLZ&S=9z0K>f(=9aaS%h(a*1>>$XcAdbes$LCHfZsQ1mQOcp~=svMX|{Sfg-$+~`UmEl!8m?JmauP-f3C7SyJ# zrT3?LZIwDVag6tBA9DCn2!|H!Sb*gnMN>&`C z(1%P*&IhC)A#Mif%~V1So;|V7IoyGI%W1hcQOT@#()SGV{vw<6W|kl>rbXaMBm~^K zn$U7^-SFW0!W%uj$NR>m>DY3)V=A9!|M=MVJpifi)2-5&e8Br8>DS3S(`$wv2ow{a|goG^f#Dr#(`uA7oYG;$I zJ&d}yU0~avU~Tv!mg()96u7ssKehQ^F{3oj$oeyoLtPwNEN3x|oVlz*`S#tGVS(Ia{#|$m4gsY9d%Y<$h5TrOE zDD4q<^{^^Z;1SbQwEXP!icGqb<4S7K@EFx^UCkfs?%)5#2|BUuh0jX50f(a|;Y2gH z^(6$3iK@WK*~)Gx#{UT*bi_iz?XXCY&q+?4r@wfQ4b9_r4>pA{0ALJvSu`fxnf-WE z5EAB25a2vIu}pnH&(WzFXl9_s0}Q*fRkk+dYR(&&AL1gJBh)e=!aLJi_kE>YDL>BZODG= zG&gI2Kl%9bcB((yqoxZAI;#OlY!sK)0SnyF@e?9m1c|0}aB;KpIBx8<)a|dL%CryQi*d4(*|TJ;zkI~KwsOA0`F77Ay}SSvaaLGbIX-RF9D=aR zM(;5hy$KM#oth&Ny~sUt3YdM*=8?gv#t*B)XoUl>{W)(8U@`QuAQ@aLa{O@xIa2Zr z;=Tu^q?LfL;xb5f`HEK-{t1A-JKxI=b__mepCZ9N2?NS-SaFC}c3z0s8;bety?;J( zFa5CLAthG84w>|h?02taDOMwQ19Yo0amVBR3HjMQ>=QfI(zO*(q}QN1e^>LoNARgn z#T8B-txH%ZS!>3YWlpZ{iSK~2z8z*5%0m2{+a=eL*x<0Sw_8`gdE<8_gmfXEYp~jk z7_9F+TrxV|djUb2>}-edm~s29M^_3e;6B9Mb%+CmQ~7SN1$q@(^dQbZZ$wBnlVko)ee{(4Cp2I$Au7Xhcg?*2*BJ0| z5(YfrJurAdwSkpmXiQvoyb%dE^F;!+ImwGtZCcU(V>qxVpm!YLC*434>lBk(u&FnP z19{0{U>V|E@*xS}zN@4JaiGbKub`6gz8}JH^dOlbu(|dH6Am}RtNz=U+9h~FW7Ey# z;00wwUQhw!(?B~KCpwvZ$Yr-JIZ??OmYgc-(R*{>akeYBQe6{#T9kJuFION+Ly2TP zFb%=kf9W{WF@GWZ`S(Xsja^f9ZL096v5nK8{La42xqF;xun=N+PkzL#lV1sZfFiK4UK)zxSwFLL_q2FPS;U>HXROMknI5Zz z$s$%gvJ$)uq4+<*nY!rUAqEl$J;I$ur!C-gHjT-l#YIHw<|XSXWFN7OyT`HwmGDjNp*AgL1TDd z-#sES6Z5_=5M`3Cj(qSkdFDticf%GW0hQR*Ji5&Mia>I$g%z_dvAAD*^b`x1(0%9^ zxT&=xAsl`Os}F9HZH*un-d{X*MgLj3|ES`!HdrGvt&D%W0p~b?#9@G?wXdnMm!_$kn{tL7Q{OcvCttyDk2YObyv{ z@UC(;&c8X2dOmf=tvuAN1aYQ90uV=eRF$#LDM9(X5)$(S1}hZ|7DK8!XPgZo!Anu7 zo^Q_>H}xY?vAnE@ln$(4UhDBd-grej%x?tg!}zcImrDW_sQ`p-gU#B+(~6K?YRoDIunGYd}f=-cX!s8 zqZ3u$u#+1iN+`Q%|6TtyzoE?m$;1I{tE^U)Qp2}o&+zAi8L3v8+p^uXE%f7v=~2^I z;p8?rpDLF*&)<28(5}JoQh6b>^$v246It$E#xg|1JIeIV&=4PU!aqpB*$eTr0y!}T z|Dz)1%Ueq?-tCeO1%7(Q_uUW#FuUVHWErmN9WtN{vh-%k@9`+H8=d~pZz$AO8!EQG zDftu|v{{9Ssmm9<1uW7SYHaO*albsAWv*f{DA68K~Ee zg>)JN@64_~HJ3*IO)5O_BCPDy^u+)hF6>3_X~x zQP@ACkK6^Ed!4Ehwk>uF_&no*hg zlmtaz-|Ie!@a>(G7^r%u!uT4BR<60kjZKv0-_8i>((D#`YF=@Ffuotb1P8>h|0*RQ)9e}E&u^~C{&a7w!$%9f4bHs5BY#}`~EBJq`DJdBh6yKsiNg`EJKb6?K zBrS+7>J|<{40{V`zDnZx?t1U;tM#=_6Z?6H!!dBRw~O?igNfyTNH_oag|QsGgo`Zj zJFU>wX%;Cy@&!OshrD1YKSOU?e;dC$Xv3{G*ZYE>3>1U*L%r*5U%qV3eO91Ubh+pH zebZ4dOH&nMFQ0LK`Mp=2Bv|pqtah+|a|fft+Nx6(WJf@7wq6bfr!<{Mr+MGqUDEgX z5nJo<%rMG75_#)UshYpPlNie4pcFilry(brv&|lAc>4-gt=<=!&FJ~$6`Jy*CgPXa z9V2d)t(`L1tF1TzdSnKm2S&i3(+a4>;oV)SGW>v?#UNgqsld;=3j07x0aw@|^x)J( zIkfeTjN!o85tO^HW*|bhB8Ul{@(Q~#2BdNFc58r1U@WfLA+1Yax&6WDP0hoeT_{Aj zWVWn>2#usJBf5jATJs=mkuW)t7QnXV)0}F|8AJ%N*yil{+oft%y5||^p-N{pVxFcm z#qXcpW8TZK(^?%awv2oR@8-)n2XZYco=Ps(!DjCK(gJ-76+Eni!y`&|{=6XQBKl^y zW06$z`RjthMk#q~KE&66bDu0xM?_iGIBnKW`7Q=*AYpAa&M z8NN+x5&YL{s8U0pzK&=iDMgn7G_Y-$KkJG_N+TK1w$iI?4Oj7oOYC1DQmK^1fcARo zQ`poL;)+-Sm~pV;`}QGxAtr!8<_sp_Jw^>;24MiZrs9Q|^}RhI?%;cuXI)WuaoHX>0dEQWjvxxs328l&yS|8+^*1$UkF z1s>BaP+djQ$ex(YjFhGjN`!6DW#Bj~^dbvCB&(@4MXG#t7a5RYLVJpUIH@oxtAbY; z#Z&4ZQ%X|B69GJHKk-aDPh#V>#0i*&cLV=2mmmh(E~R~#YPIACE&Pz2?`sWpJSX1y z9V1Ta;6RbNW&6!Hdxw0GN__e2+Zfd((a&#+z*{I`z#aVDo^&(Y5Y~QyXKv|0!XGsG zN^KM&bt7igt8U=UY7Y9kz!#T0Lp!nEjgMjdLC2JN44+l+&hYgEL^A^HFtWE~8T()u z^?@)v<$3htRP1X99Z>?Dq!WJC=_hYNZ&}Mbpdb1v#12Z{GmvnGXbgeBMpV53UxwIWu&j9nOEb)TC>awUsCo!@JIKhSw9yUdxmNs$f@=+s1^(nLrd^W<1 zpFsTkg!)?{*vI{?t@!KL_&BA~^Cuvi^aR1q6&O(RcR)rF<&TB5o)xFW`bWw9M8@oj z?LPfDy;SMFzHjBa9{O4WV!5|`kAV?+hAO~q&uwu4v(m_2{D{1x=x9^b@!}O41AVSUbMz- zWi%?0pYq9iU4V=ZV5D>o z=GEoR6s{Lqztqm2y63(DG}2+r3Wev65FcK z1*aWv-lt)bgewYj`adM=+XYxu%$+I-Z(W%C?IMI!oIhs~K}t0Y>Eq24&w?z;?Rd|C zPJf8z$;83#`YQlJBgnTG2h|ai5K$vF!IhAPG{r#QC|L)Bo%_5~du9&%*ix(k8tnd- zm)%3%K@<{o7LK>Jgq6-K*wFf{!5TF56e5;Ja-*j^;?>PHc^?0})j&!~ty2;uiY++5hjt=lXb((|g_pfS4&F(Ip19vJffTa-Q%+dS#f0#Jvct81 zu9jv%9!hFDC`tZ#yT1dv&{Z$!S;)v+gZ+O%QTXj^$@Y{{yB0+ir zxWF?xImlKJzpDVNPr53u0D&M$Gxgb-%8|(S3?ZNiOHwVde}O7(g|%-{OkWL#JNN2x zEp8W?Jp}8q4y{99_A6VfZTxm%SrRDgUp*FnQ|D!+4cHMnhva&P z(3Rmk>^Lbc)VQ(2s)`al)cE`29{`BD+g%A%Z;a@v?zf#OQn&R=?3OMhF3<0>WGrLS zu-T$c;T)7czwaY>t_&}kE#MjJd6POE58#UZAJ*PGoC~)71I{Q!e32x36d|HyW*03= zW@Hl$*?Vu}#c_Rw-_2Sbr{h(`GX47Laa z-^j9=cEh6!frH!4!-=1`4*6VWqL-xrjxqp-WYFrruR<}*+*h+!ZR>dttXXYkT-ON) zvkdCt)IEfeMxpU}60#a}WrsFW3Bn!W?`tAJiciO-@qy#O>ihpZXWssravMcoSGs(T<7 z=kc$pa;TmM9G|oUjYYF~FuK12KIn| z*v+ypVj%@1VTm)1kK0**Dsw9sW$*Rx_QUkg`$Bb8$|% za*z2vinvF)wjGZZGBhtk_+8SNqX|suLAp~W1Q!l;S|3iAZy(MRQ3Hz}R~vW??drl$ zqZ!DR8V>J3n;y6^xJi=%ye8Y{m*Kx`)fz21wLoRBi7i|-K$b|RF5jyq~8yXK08{?+wA z_#JCK9DjNsgKV(vNCY;4IjGuhXFum~0cHS7l6Lj3jRF;FM@v!9F@=uP`t*~ha z011wt+q#aO04FPNfmEV|IOW%k8pkO};(7tYim}BRnrwwaa|1Wbz2aYBiSV#{00sqR zaM4O@u6px&L2S~+WqWo*byqDm?+an5Ql?=o2E;dVmSk0pCSx1La|VC3#SDVnHU` zX*RY@d}46EA&Ng^zPKmD(6_NJ^?RMxFBFB;Qp}A_51c^s}qY^huODzC2LH;q;0$Whb z`51pd)W9w9!+Xbf1kV=@q}whu=Jd{OyKpZ9UTyI3S7M#dYKj(BunS#5VT6(8<-pOM z0OpKBg5=@(OsiEl)~_=}MK1N-dhUy6xn}sWdJ6~;e7Jw~I2$I52ZccaQ|Hk>*fCJ# zRS4XU_+JYWh7ukQXm*2|uA8VfZi(oEfbUBMm>ljOB115|aycVLplhzU;ujSwKXAQiuf=DvfE zk_#)S)%qF_m}A1`1<(#!tf#Z693}lxjyc~aNojTLCjTYX5 z5Aa`RS^V>fM89@Dg`HKc!?;7>whG(g)MU;Ug|Pc`G}y~vane0Q)h55a45AI3M5k`RD%pZ@oaM0pLtR1g!O}Z zKg0+UN#N6*CV!Ow0(zE0d{alcblb1$WU>$7;3x=h1!QbPG-Ur}sn#3XGV3&185HMB zOGh6}K=(lmTPO2F{@1QCXv1T9OjY~`BbQ~#E-ejj%KQ5>wm><@mLw9 zx)+(II|TszOK9zdiIt&c)Q4F=d=q?nS;3+x|0ZSZFv^S`)?}F=^bV@}LIl6zew-_P zH*dQ98JTs^iuhbKYAeUE1y8Plr4=h!7=%F2Zgg#LEyJZA;4I0oD6i`8kZ1zQOV0=@ zI4$XtMfAPD{B*^_c}(x?UiF}D2-cXf2@CPtuV^lx5iF@55lA69a|*mFf6jDq2)eaB z`d@}!Ssb`qH$O5g4t!SO(ke~kOMx9w7`K+_Tel6Gp;>DvF^#}F969R>4DBx<75Uh8 zC)el>oI3gbwapP1krtEh`M2PkW(7ZV4^kiKr_g=V$P=T}QaDFam#XN`2A(Fr2(&dm z0vr`SvJYh-*TXvwSf|^ES>>x*^N$O!9~UD8`iCqpwzI}(yRs1dnCgVjXsZY3`cYwKzAN96NGELY z$qC#6ZOpwRbC0UZ^e=YJ-@YGv{~b-nT$rv9`dOfh-@3d!DRNE}+}UQqk?s}hS=wyC zN{bm-oG)h6Kg+4d6+`7~rwW|Uwz8tMr{I*{3tfj%we^V|Z!(pjEZ@=WW+nYzL>N%VHrl zS$a&m6nf8M>ZD%U!4+2sg*&+r-_Jcf2sgnCwMO6&EInLFZMpvjt7^}9?yh%N@?9C& z?~)7TOIZJa=alzX$(8TiC-nENh7>3II&_+X9AayToa7Qz)80ldtJ z7r5A0VSIHx?709xaB09-pT+!#G|v^c6Vs+8hh$^=&>%fLx|aDupG>$641^E{A7l*u zDk_hjAR(9}{=l9K&Di?9gSwt?6(AE)?KCZjr3Um6= zWwpGv!iW3rLwaFDRq$XvbiWA?S{R%T;fJ?D5R+HB4hysE0EN;{82s4#d-_Cuq42BqmVW!=krNVZ#O6Rb4NyYJfwHiu{aRL*+WV^=9z z@eB?J_DyI=Z7>UzfWk80FR~0p4hza@s-)eT8r)TK&fMgvNuPAev5y8#d{yp zlq)aO&(_BXqvRp9=nTq2V>Cn4OeaK6aiZ(o@wmOr#f9pkQ3!nUyh~Rkl~^D>E|LGQ zPu@Ci8#pwG;OzYzL+TSDsA+lh(^Brn;G=|9$eurnO9R{JCj~h`D?~|I(wR`&!(gLF zJp`JayQnx!I)8woOStbopr_FK84g4PIM5v4bpR#-DRoc!ec^eV_F_8=8K1dCx^wxy zf7aTAx*HY@quq0E!>=Om+6rB=c0kp@0$4L6F=C_%>#qP(iA-T$13TJ<_?>Js6nmDC z$43CuVhC4F(bND`G!Zaq(5ZI|JT!e(^RVnXFSH!EOOGvJ>lEZB-nj-w2mC7%j&$4M z(S1OGO@wkTmvdUAM5zfl)=MFolF-<>!t2^MzrI@uCR)k!LtoDsNlr6&F|mZ!`LHh4KIE6od@ox^r3+yIJ7J2=k4<2C`qrik1tqsKer08j?9+U1e3TpYZQBq50vhWfr@Ki(aD~4yOd`ke=0pq|B?SQ8Doh_gn1!u;Vu(-B0y=h$T;2rN=tjnN<0jVduM z?7v`uxD`DC&pHb^+>HU}A#TqhN>Je`K%Yk$k$HGPjN|t4wp^y;+YV*8B?ERbJfCK) zKXA30IPB8F6Hx#A2{1W$0+vzyD=Z&#o8U~I5OH{OS`4deodgtP;c%F#wGm&l_p?og z_3iV`f^fN8UZ1{849`ODf8kjW$CfxQTzA6*ggO>9(GXT!y;hk}_y*_@vM{>iV&lrq zK{Q7QXi;wh#5KVZm742l)s{K(wq>J;@^WZa6u4mJdEo^TT$ z1qk{*AP-kBH`2Gy} z`61!cQ3Y}{GCt@2)OYRUFF6m1{qjo}{32vQg(l#(eB3vx9cFvmq;S?7 z%x?Nj!_u5ePgUSH@l{x?T3qaUlHZpRmNuISPDzEGAMQxC#9p2~i2^!ucdyFbs?lV9 zZ2A!b|1!y=XX$d|3ocbjjbO(!N)&o`Awon z$oE>{Jl>=SCKc;z5xeYALjc;_K?^qa^rhsK=Eei6LuZx(pet!@%ho1aiatazxgKUdUe&)HY2-*sF5uLI%x| zO8HNRxWF&QR6`5HtGaghSqvWI^`CA~Ain}OZoTbP7Jm=_wpEk+LY$JrSxb{EY`@l> z3G6?w3h9{~3a=wp-waM3 z1#1CIeeFgO$?^X}-zF>0iaDOO3SFTpRL3!a^ItkrlsM3K{N!KsL^C^i(apr0U}SA3 z%1X8ICjK|G7o(405_3!G^v?@Ig68AClPsD|L$FgyszO<1XUF11c^^qjIfxX z=owy+_P)hG_#mg=eQcTyf)CPWCn5@Whh+))zbX&A(`iR5k%IyNG|ojeMgM`TVT&`| zUt9+xeK$W%qQAKSY;cT99AR!WA1=od1b+K?bCe1gv9bW&=q1nqr2|^ftoMrRetT&n zfS=-+`i2m?P0P>V$;bk@FlkQB>?_Tap4dO7(ko~EWPbDqilN88_UZui*oH;#;AUR^ayNmgf^sz zr4~t&tUWEHnIt*abA-(FMtLAK7yPx-a|R$1e$ZdU46C>>aQMW-HQPX@s*?sv<)BDS z1Ya5DdhFd2rMUPWyZ9=ZDd;9e7y!-rp0Ry6jdggB5Kog2pvQPYn+1)ESSr;tt3zp2 zG+?dznY&snjc>RVv)?4?y6j#5(dENB@FsW{?!!i%5(e?$NknQ#)iM9Ru-T~R9E|d4 zQT6fWTzw_YM_50E3EM10O!R>1xlk=&cajb6Ww5EppMQ|+X(E7LD%g>7e>6%v&DfF& zpQg$AoWe!V73ND$TXljvj(QB>OV?W-}tctLLtzP)5kcjXWxZvkb zNkz!$$@x8y-m*B}^!oN0qSp5FuP#LaZ7O$Vn10eIBK7TkBs2kqTtkl82M zh)f?8b)!}Frm5!EFLOI4=jp3+(f~Jj*viDi&k0f3AGfiUG!*;ABEH*i{WLB#9dVBj zcJnvkF5P$|viq~6FK%NV08aUBm&+w~g8})-n6yuWa-H7SBUUsz_4{@z!}ZCKLIxXg4#Kcqi?U^wbs=J(MCyl zmOGwjlBQ%>qPUf-!Huvz)S16cfeT=S$1?M-lKX|%tNrH6*}tB^lWRQ`M^z@|K@mbz z;PoIY^xMU!#miTzpnP0}J(#ZQ+*WWC=4e&U^;~)lre1_?!9Vu~QJY9Q{aH^AhQ~Sv z7!y~c*S!pbV0~!sirLO)=og@Of4xS7p0r0by`Wf#fnQnoP4%Xvls3xYY*$oqlR7lVE=gZ zjv+N%y*V<}#&_>Zg0lSF`g{=R$YQ~K7UNj39t$RwU{P@k_Gu?b&n-MRdzwo4?)$`3 zIm!$dj7oJ8Hu0}#)>?t~4A)_iqd#BvmB^>*7`>>*JGq!se8(n@&^(oEGH{RApy35$ z3@{9Rb#*UK!KR;YMQ{FyLkS2ZNl8R+=_?%e0YhD-(w&tX&3I{qI!~!QX_2hM*RjF^ z@TW+OBPSRbEb0S^>`t#JSkkWm`Tt?%v^`*!)VmFV$)j(!DzI3-OnrKSvw7{$b}D+RuL)l$C!G<&3+{I@vP4bm6iP(fEOnZ$N9Z{939WO z1%DtpCi%tHWurkD*!YRGj>qfIs~q8{c}(4`Xy&)R#~7()J;Yp|P;=$k@voC*LO#F@ zxxsO%9m@~M;N20^?`HPc@pmg)dS=#a4eIdTEe9PMn~gp9-zcOE(XLv$Q{WJikYCuC ztaF#rld3oqe#Ez+-(~4ho$Q2UDK?vaGoOyfJRPew;zN+3%?wmit$;K}z!G`LtA+el^cn(%{`Dok0vWv9yD&vxM=SKvYQKCB93D;) zm(Re|2^0Y~ZinYd%PK5)0on86mp6tz7~S!an2j+$y@9)PANr;nfRi3mL6iKF3cs-A z2>UJprBIzjSkVU+PEDL6L~Yg05SKs}))ch=*}%Yyz%cc-!-98;0#qb| zu$-8&%J4rN2YBT+;EK0YbAhs(A7udduS>cGYZCZwFM&5$50RQrEV83AA$v0$OjF(B7a7Os<9m2XTyc(Z$Ia zs^AmYF#y1k@&4Vl;sy;m;wgklAnFm13$d(6l>w}Hsg||1EDS|5o>>pK!UT1=wfekR zt{#T5u0$D-r3)-7HK${@eN;=oL&?5OMof9>eO)nMpT&Ke9l}t%h=y$rv2BkS(fIev z=EdftcpdgquCQ{3KJJf|^BRR12Y}?Jz~gy|qk8lDfh6i_Ikuz?Jb-VYZLMj6HTgfm zBKS4NPy9_y3s{RM5Ip-%dYfY8m9)hP50CejEuH09rEIFmjowE=BInXMJ z-#%Mf0Y=Ai$gwt1l&6k2zG|gV%xSa3>{@gQ7+Ql2<0Xe<(@pGBviP>b-&@juT^^8F zMv52pFM=cZ6(P$X7&3p@Jw9TZgdgcE;02-KI>74kWgTE}>O%u~wG_bR22f4t?mV%& zJq#9qOt57+-#%7e0kXA|E~zVohk?taGA&=wR!AjUU3==HQT%18YKt$XU!VH&5m8^_ zWYM(tY-x=VW>t+l#!cc*j=3iz0{GS|iJN+WSVn${Ca==@TGVrrIN(yZUujYuEhsm5 z+$2~|Fdc&zlQ{XPF0HK`(ees3@GMp9%vS?Kpat+AhZ`F_DeTz$>1o{acZV-Mf&1ll z><}$8aQ|%?!&CV2#(ttQ2Jod~(}{cYD%Y+6eECIqv6xw);7ZbI-HhkgU1DAsbg87p zmI)>MAMC#wJ{-(DR%J`zAz`%cD9||v;QLkC^L1<*1)0D2iB&4ruz*KpFz+m&F@9>= zZ1I_sBm{glnQDCpW|dm0W8qkc@EW({01m|{9o>3akG$nf=UItJ@C(vkI=o&#iPMu= zmjYf9jE~NtWeFs$8yo1*;iVa!OBQP3myF|lwq~hAZZIW-)w_QZu zh2hNb6Q1$j_SpVKZBifshOsYnE+3jNx0eD`T`F4#P=v~EN``#DR$x(*D#dpM0%XjE zAhlxKt)LhY=rOGw^$r&7u-4NIY-P`gOPN$jX^?QqA`_|;p=y0nIdv{#8vqt%pwlNe zdoaCz*;e?>PASdqlxH{3r%Ijdk-ahDRSU+ANI?t3b59Dc2nXattC_7`;T|%sjq)Yt zFZ5IXSY-a;_ojJ5v6(++P)s$D3LLw-h!xOoMso6f#MG#h)Bw^Y&hO%VuKXboP#_!1 z)UcSbGe@G#**xUHrzyDXW?aHtzESkx#(!a9MCN;yAyjt?W(kVNEoKklx#iP*V-wkZ zZ41yyH7LasEdJrjU5(L>q-{L*h6u_aLo=auJj1&VuqLdkhZb0pyxPwY(_SAV{At;W zC1Dj>s+OL9jlZ=z;h%tH4|9-4=L-0$Q!$z1d_C>UUBg?<=1X~oaTr%CvsVbT99P5R zw<+i7p7#x!6v|9N=e}K3B7+Ig~-Lm~XbrT>1V|WhXt4oP90El-) z)Hm=PM6hA#JIcxvN^P_5e}Xh6Pkj3rpwX?2*OT)QAsUK8_Zo}W?-bVmf;mVwJ1O}^ozkm{67DA$YoB(Dk2S$~Mu<8U!Bcgbr3EAg z;mE{tgUJ0TM)^Xv_R?R`Fs-h(i0)Lr(W`PvgL3JpC!%Q;(#p4Uro)91K-MW zK#uWHE1)$(PdRvYvfK^lWnsjJ61fH%Yt;~?xP+cUsd&T+39B~2h51L-E z2-5WK0m6``kj=tlM`F@tVB`}JU%jBdVUJy^4z|L>r^teP*D?TI;{`?7+oSz)qjY`6 z8TeYGnF7B4QC^gxt^Jq!sZ~;yo7rZ8XLe%F0=TnL%T&@0+iSC|J#lI3P@pEj(Syj? z@FLc&aTnO)=|j_b)xLiW-r~B*c*A12rH;NV5vm}`4$O0or|Zjp+{?1m@VuL&Dq zZ>a}}i1%8^DkYS7mM}u=HKe1g8a_ET1CE4@6NRKgD$Q5W`h)W6epbWOx~;(k{cl4D z%hSVM8)++B$q${AQfPisa3okX%>$4_%KNm!t$b3-%VxeWsoAFTO36IHWUp1OM7>A( zMzuNAB2O!H3M0mRs3(`n&)hs8`(WBBWm9HTP!@~&9KAT>5ncGCX~lpO{)Ra`PgYfqm4Gr)a{f8A*1{8X292(0R!VN4FcEw zR}DX#doWb{a6=?U4W>?5WAKVahvf}CoFfEkL?YKkPsBPR4ni$ z&(qeWb_Q{)zv@FFM}5l%v+!H}`>Xtzn`Oa}76|b>KdN_^<0i=EoU|4w1D@nVCiNnn z%a~dBqLB%^;@cVD0jW-cN0GG36zMQwqmaAEps)lQv6JqaKwaaoOz(>Wx-5nsr%ZO& zq}rFX{j<$e59V)b5PbJ!=gDbHVn$QnPLNStpkc8ClXIDXW4ILzu1Qf!Wxi{|IpCB5 z!JW4>wD^5PBm$Ts3+xMwgsXWO_7ac-!3+A<91VUwS*F!@RY1LqP4fpM>2F>_}1lsH6;>4O3 zC^Iz$VTQqLmIG9jdcMNojUUMVWf$B>>PyxKsmZGfC)nW3iGfRMTT!SijVPg}@wr`=BkHs!b_)^4@QZD6#IZ$m4*f>Pa zkyXP_Hq!r~L0ur$XJ+o^e=M#`hS1d|H&@EBoSE78DVr}-%rWm&C8vNMifMANtg?uL z7TMUnYsCA3hU}wQ@;*`_n+qAwF3MLqnge!^fF92R{4%o-*}cO<(AHdNdQ}w{?3IC~ zL+8KA7^^d#4=x2Eg?WW^_!Y?5ZUcYyCm4u$i9EV1I3#_=dVxNA=jIsXf{H*F%dDbd zcEx+9xS;Il6!hG~o^NBn`7g86iAB`?f4Wa0EM`*>A5v@VZnrWX`sRwiw{1|(J1{+z z+9slI%_#YGPhQ;{uh^E2xXRraRFfKAuZ;HZ3r1aiEvT7vC$m@aPwW}+^yjXxbf;jb z4HAKQD(Ld+nAayDb6*3X?=W>sbRYDPNkBM~%TX)6ID>~*7pQ#%{V#lx?yh2i4uH!r z01+R$Unp|=tuJ093X)31=Vpl&b?V#T3Tv|i8()3+zg>+aAU|Wo9qL7m+X&Wz^ebkz zc98n^3P|DgkW>HnUKQGsybZrmR9s6m;DlOJ_v~~bv8dL zgF%sosmJ8>@!uD-=M~CEkdY?QxiHspDdWE693$)y?%8wfL*588JgfW-=(TTpkg2-u zgOrN2kN`@i(j#BZeP7Asz`E49a^CPxFk~NJ1Hd=Yz>me!8C>0n zMyZ!`SwGml2~?3-d*uxCZ=$H1C7GbiMKov3Q&o;IE#E7eU(O;E4t~YF`g=a^VZ}gm zvgFZGNwV>*lYQ=7EuG!~I8w3|#0v2VOL_rP9Eu3>?*0XoP5=WZ0n_hJY7t<4W)ak6 zc>ut`aQ-Bb{?g+JYu5mitY4T6nA=eR%&quh@c&kTwg4`le<;6(IJrZLsckP%&H^v? zD}-Q)E{!HnBo^r_J)$L`>oFFQj9iRgIhR7*&9xfTRh`^pmA% z`?}}+_x9E0M1jpo2*?dR(wfh%_(6N;qj{;HHQOjuhoEeeE}L_-W$ehFP~`>&aZo)m-%8=%!{wTH_G|=Kjus^ zEbi(SGqzKg#-%V7ei8x4qXt)|S{GTO!g51#SLTjK0|#yXil@=&;n0Rm`w{K|P1oG2 z*T$$MA|`+bea_l!cOz6e+R-0KM3Ue`$17UpJpzb`m_E*h(f}4qQL|C$Y2drc+S%^i zhBjvY3LpLw)vh+n{zBc-6kZOgp$K-AR~jy~15 zRgm<5#qgZNr$+umiT$rM?XT_aF1k?1NmL)wJra7;OKSvsPR^^P**STMRpW<9d!M0z zKhhKexhf!-F)@Ykg&IMO7_hfipv<4LYmL3k0Xy1fn;qhg7}kMInG$g~>>w98xQ@*Z z<1iC^Bv@1@u#w8e9;E=A^&%L{`r^PX1@6a7Abbn{y8N0~b@}>V-Z$f{Atp>y_SFmv z7QlY4r4XO9>H`Wel3b*l2P=JWQ>c=~{ZMfztS>)TOUSdH+_ckwR(Nu zurF04Vw~{S6PTT#ey~_DMqE*G8Y2{}A6o5DdDy3EGxPN(;01RQq{(p}_T?s{>{O~1 zoiU39NiSr~MCHh-oED*-V-F=)as}?m_z#}s>C@0x25_jqq(glrotYuW9~(mkI_zo3 zvfx)US@|}w9P9;cU%vN|io?G5qvpl-H-k~|Dq|e5gSezEhv6eRMtT|v`p>i-<2kF! z!sBhU{H&WBU@pU<(KWa#G;aogz2)C~3)$#|;4m=uyW%kE0K5dq``6JA)UogHQ8f&~ zLOJv?P~H?kW1RY!s`L|?P#I*``MRou%NUZ=hOc=Ns@#O!2Zn4CLP{af5*2d7V4B^e zF9*I*`ayI~tZto5A?a{UjxXX zDY-12mq)MMa)r{{LA7&bJL;k_6QC`f#COhW6qq($dOai@`~t(&4Gz>6O*PlNZ3W|r zbF`U$Md>uNgoACXoceqBk@KY>^emS(G1U1)f8VN0=_-vPeoT~8@T-GvvGv-JY2>At zZoQanU3>J0mSu611`;f63z{<&UwUj*x8w2B59+RGD%1Q*5&Hyqij8o_(YW_&n>`RB zu9l_i^>I^#c>OC0)=^5K#lF1M0BZi0AA=jhTBVLWpM{C9Mg9j(VD;AVnaMpy1*uUA zpq7 zhymt`uOzf^^v!g6DPYAF(UlDhA-GvdVE1?H#;K3R zsLzBD3O4A3KCvro(?xyGA}xllowNgQKukSQpJ@WfcxPoAu&*)bpfi8IGwo0IT3C>{ z(7@uD!3Mz8jo@ZA0oN{oEtle~Yp7>Sl~)sZXRv8GE_;R2K6;ALYkLBA0oaVmvhNzW zm7tN>0c21s`#`_QIr)@jXU_`wd+m@ba88~x!6I!4Ry18}izGhx^B;CKm0<3SUF3c!d8Y3=f^peln!;k8t8C%qI zkIp79VG8ypbp@Hfxd8pZJ~&fa09+kGP)DB=tMY0IHHHF)I4NTc0J!LsDFNp+*G>{< zqzf!LWI{4W`lEnAu$%9g7bQ!92dBGl3lsp=mKD*WP#k0Tw7KNLwBYCg8YuGFi6z!@ zI+u=`g#N$*74{Lfmqk+xoKvE;?0Dp>D)I!%DZK4$!OAnhbTlLiApd*bpP&lzg5>L5 z3}Axa?I>nUdF3w<#`uU)5w! zZ3=Bk!(r3vX=~!#V0!wlT|g=t0g$F- z$~TcC8ru4;xD3%KG_R?~SniJoGOpIhg9>VAE&_CYXT%dM%9zWy{wxIrxa||p`=D}s zj%bK5R~S^$1fIE(aidULJ_2YNg>Z16LzBokP74O&hq~*MMB^{hoh^$h7 z-hh*E-KEm21>nkLzo;5s-@3gL(AulUTUA!{ErO(A@NM&WREPP*x zZT)zg?(Uc62ro989#ke+?i!{#-LCIE0s1=lLD$PN0Ms$*a4H8sk)j)>JgV&%l~>3< zuFMR5r(lN>OziMz81zG*I8Qksxu#*FKVUfAz+7e*;H;5*-NT&+6kDS84QS;?TH%tO zhBr05RQ>erW>{N6?~XuuyPK4{Z~?bNY0cynI}6P8)F4)8Z->d0($eW~ z#T@-AN|oRE2P~h@S9!i!K01{Qy^YX3^fonj_eJRvp!`HN0)O|d@(4WU^Z=DfSqNp+ zrrJ8{(5=B+zJBxnN?AQSD)+{#@~mYn6K&laDw>RT=9b3?2?zgPkb<|z8SyNOF6k?2 znhS5g6xvU6h5~ub1(Dapjx5_A`Wg>XMh)=eR^*0O)`GkeXi95Z+d($$iNJ ziqs8(< zBwKn3%iF&cU;oC*7~0Sfz7MauIi#32!1QyW8vD)U2JvVgFwjEK@)PYLfogxX4|u|u z!$6fP2wea#&=8P(G=T9HW6xrmf6}MlO50ZnCSA^j6hbQVuz(W8;0n@9Fmv_hTPr_!ir zM>l1ObasK?Zw%fzEp>s^te8XB4BoccvCF)A!t6E&b3byS#L6^T}WLj$*@ICAd zf#%4l9{P&EpymfPopAIs;pmh6KKfd?#V6yGw8W}<3xI;uXnr@yF7||TMDXU;V;eeu zDSsXn&(rsQ^j{aa52e7)#M>GkmeionmtzG1*K7Ln{_hFetdB6kf_ot&+ElTNKH9Xi z!=6X8W(;rD_F(bK_YvZg3Q>|rpTAxY#)Eu#X%WWJFaQvx-Xcx~EW#WR1FWynMB=>l zPX_4XHZGT5|6!ArCr?PMpsbL37@Th_zeHV;ZP}TpyKHxjew;lIQ z^P!jY`H86oMMsw2Rr@2{0s^ErJ?STZ3Sfvwmx>^iPAT7H;Ni}b_rXy^jln8EH24l? zR?U9eFIQ_VRfF@~+^!wmHPgQiuAYR2P}wu(@D6i$^oM|~zXCwCu@VJn9+vvQG=Ox6 z^|Q_5x3HuX*kgE%*hQH9y+l8Ht7%cGh5t@y%r+h?F(|ke;H@SPf+a>fPQ*6?Ebep} zxfyvc&=Xqj0VDq*Yl76UNdM1c1Og@B9B0yEZc+RFz@7Wy7eA*(8<62Lr*;9@Xcyo; zj9%7#NQO@a^5l}>T1bpfe9~9DTMNtdHIHp~ z2K-xnOlRprWMAN=FRWRc&A z;`=^s@a#!Jq#PG8;uwcHSH6iW@FH4ZwMT=`D#jApUVg%(@viXmxekO1-EVjj7a<5f zkxp_NNK<|%IZ}rbUbYFYM6D<4wF>^)j~Y4~q*w>=5K?y4svv|#dWy|*kAz1U%*%Mj z*WrgyaV7*mOR;zshi6TeDOuU!p2GdPovb>#=K##ywPU3cG*1NGgp^&O1Y285s{pC{ zF-eCXIH;WzIE`4Ti)+V(C0${>Ca2L(`VN!QxHq+luirXc?r=~D*U5Gy-JrCC(IUpX zI{Y?cZWc6#PhSK|jUpaK9v96o!(U}!YLyM7J?6`U6{4oYuGahNFoq-pXj`3aD$fu8 z7}H>!u?#DTd8tYIEZmD(-wtsV>6`=H_Oe--%g0EZ9do9Lld;hE(^Rfve7cX#o>qO} zYdA7{1#C}KC*K^9-*%WSLMj%Q8{ATX;R4^d@s)D_Wwo`x=s7kY(!vzLZ2gfgN*wAd{M(OnhJVZau37tXZ8tgO(u7xw8GAj#QhG!%&6WwPKs-4rx54-Q!=1O| zy$|Bn_`c2kC>>odHm~irs7`}I3eQrI$}{oold!4*mj9r5-RJ-;nGK*)n8Voh;idLh z6Ig&p9{SWPWIgx6DVdBHao|GASf>!`u(xBSEbS> zIhQ4ba9@u2* z&9Pgl4T?oD;lI9IyKxnEoy9aBQ@^L>W`SZTo+i{^ywnJs56V5ySAI_4%63-#(Wu3=&ny#UG)hz?5kX||!w?vF zHfCDjBgDK5Kozn+;&G7#!9#~9qh-~espDiPc#->v$$Ou42IHeQ%7;U3@a-dpCc%gO#Sb}Zf?lk|)!HF621%8_mRfXL4A~YYnzX;2t8loxK7N(XwNc#>A zQJxjyd&4@h)z6)5`%tJ59s7e)3BW@uFceS^RdH|NeTLfeWminq_1eWMr~7DL&8X^r zs!L#`S!8+5q0Sy?h*$UiDs(Zr?d_@K@xsH>=Sl6FrlDh^Ja>;J!ePenvO|+gl=;OJXA~qL^23l z#V5uD8IJgjyh~}@gksNuQmWh-!_pN5-ZDygpBGvP>X)l=wV$}4O8|5{ki za$Aam_tULmuJCEeDFK5F@cEz$OIORe3btiq>czcZouuMIo>2{XY8#%0i+dDaVueGY zS3+Fnp$}3bp(9Z~Ao?8+hSX!z_5>3Qah=Dlfy@MF*dp^ZN>jlY&)0rnXh!&eeW#r; zI4{KSB8*N-TY8!zwMVW#POrPVrRt5hg1D?`aVl$Aw-1kHBPix^S$X=hP@SjdfLGUq zRj@-Ex}|3W;QRm2ML4>xhq0c;2*3aGYCHOZ9(ZuLbut5f+UbAx|6D+}cAYo;SO0?F zuhg943J>(eko*8k#!itQy}*)hp^t%u94W3`U^ns#<~VKy`rvsW2M3req^cX2Hi4}^ zabQgzCpsX`XV7qb{=z_-XA5{OG5copzFC%uG{s6oBvrJx22%pFog7%5?;$p4<)fkj zFyPJx%|v~in2Yxo!_qgv0NlQmnv7H$9v9ljqxNc{wNW!qF> zt~zgajW25z7QEMhK$n(TumuR&n{3DcoWGx?cs^BBGIUkZm-jo=Rc#*-O`DW;!=v8A zh|%qQ0%SPsTW522Avr?+?+gE^<}{gL%f+2dSoMkn^F*0cJyVA*~Hd4(3Cs6Y@3^X}!_FN(BBiWk3GN)e1F zmBS^k>hQ_H8y3Gfck9A;p!hJt7}$j2NNaXY-NfYCf{E+ziy*icD)?o1-FBi}=?wBj zcgtlOuGvW2`iqBR8KSy+QDhr_uV;v>c~#4uSRQ1oygxsJLkZ!xTTS69l2dKu?4S^# z5wUGE;+iatz>&#DcFNJs7D7#NC_b9GuwnDe(L9U&VSU#nNJKh7|6v$T#5@0y99vT( zIJz_LsPWxOHFhpqp-@ckb&`^eYvA+;u;$cE#oVD_Yq zv_QF8-PyQt#{0q}P=%QS!1B_;o6`+Vuy z^|;1O@8xFwvVJ@VOa1v)B-Op-KEil`XmkMF8l;+T^-k7Y>9pI~Fq2YLf2mcJp-d9} z(X&!%o80;OJHbP?OF=bd5J%w2na*_Vga-f9!Dxx4iUybQnrkm00~p_r@6w*LrRA2IMdJ zpH=@U28~<~xh#Vna=*9JlWF|}GXllhv}svzNvUC$E0pp^095Fi@&il0W)!bHD+cN? zRS1AS>R=~)g6}vc_1pSJBsq!}0NHb#bTc$!{E}0q3VC7xyxt-49ATsjzC3*%|AKg1 zf3eM}?RSjr1}A7?J0PRx+vQ8jOf>mH4}q;9z8bqQm*fHp@^NUskNGu&Z2aBkiQryR zA(=wkGF!x(qQI!ge1`tP#L-&-?!X=J>7pM!x<$72!USD060erA>AWT>?D{L>NpQs? z#O;=YHkBRjD_ohQ>1H-E5WiUe2-z9|x4d%pp$PX-%G2B9{i~QvP|mp>`mH(Mss)Dg zf!*etLtm#6wP3x}Hcnk^{HTxp21CwKZvOyDZU$M^Up^d0~j5_Kad@SaOvxx zwY(R?O~6wLmah8K*~`C(3!uQ_u6@we zEw8l7p)?Wiq1xP$If)}-lykt)vWS8#_lJ0GTp9(pq1}v2C_`h6Pan~-TupZJBN7$1XEJ6j<*MahZc<~0qUs2pP)%K6p)3PFDqhrw z`#I2FgzY#9BMc7FBR`*rI95yD&%s<;0i(6vFa9+j0RcMYf-*780SCEiX$$oK+Fc^D z_(7niQ5h?3c}ade!Tb9*0_+k+WE!~+*7@3|<=7l_(NXde0hXhmUhX4>E$A*zPUte@ zERd4k&C!t(xr7rjTXiiQ0BVKr%k7C5gH(*kj4nO@&(a1)Rk~B(;y2CXe!HZ}(gUV; zvqTm0RMvDGPV6G4Fryy~ zsFf;TIm5KYZTkQOHDJ?&x0|Xuw|Vm)3{I;f$m|80A;JZC zkx=oueSWBB`w0>Ws|7{pE`*9JkltnthukF1Tf@b0p3}dZ}3QFG~O9c#Sis!;+uF6{x$OC1;=xERkbg1 zW%e%7nE?v}*T>>XU+&rQ5}m{<;W}tG+ZW!B@d)q1CQ}YTz?UP=r@DDIK0%oLyw*jj zNZ^&8T>pkL5GMKi^zxxWd{jA9fLB*xIWi=rN4UN)S=E|0C@iyiFdjSS*#*7_@F-IC zGx^(442D+F?CAA{4z~aw5?m z*nMdchfsW3&rKfCHQvGukm$**d)SZ8=sS|vMe;#ITJR68+R}Pzkn_fyW0$P;?%F^P z%%tq@Q)Bz#qxyzN9TZx8x+AmRIP8E3EJe9?5xOKVDd-gkY?^Bok4K=G7Nvvw#YC7D3615W zV~v1+fP#U0?Nt4Vq|-3w8B(RAV~_Kif@`m*QIo+yi*L*KFK$-dg2*wS*4#(kT!t!W zpM7butEOb}^P=c{CVftFtcu`S`MgZNNh3Ivo!>gjH~SJMrg(YGb`~ z^;8Bl8u_5s0S3rA-e*q1I7>-(!47+?%h%;jE!)LpOHPeYpEqT{0v*J$>#%>3?E!M^ zSZ6OxTC(s-jlG@Yr4%l@;fmGTvT;O2+!9V)IT}-9+;GEQ0dz2<`^lj3M1?muzBawE z_08|)13+o|Kml9<3HXJfV{o8m&yv9<{Ddg}xwy*?m#REWqBVV<@7!i3svJN|=Q{zE z&T8lMYsrqTSH_tpSQO{F`jmZ515#9wRE#So65s1KSTH60=SnkUV3|C?9$j620rX{q0 zuH1cDBq8Doh~cQ<6WsDAeDEH(-N1@w}%~f05=+#ZfZ)tWJQx5Z88o!(u zsVU=8K>oxr6;iPF@Sgz)9XsFT~xd-DUM`y=A)Yd*L%=O@s8cv-qSVFWmtub zKjt?b7QeH2TE%R#?|Q4C(#^#;{#+}3K?RG^FV@%>>=;CG-(c7hRUZO5^`n(&$&z7P zmT(E))jHj&q_X0wkG;w7Vd13j+>w*ZhLA@S32&+EXL=^NGw;8tAB5koYNWg35fxV+uf!~{A+^xO7iL&?~eV=1z=bD zlJ#>)$?f4l^^sfWNp`=iB&u&}V`x_L0er?3Gw04nkAz%}_Cb84T~rav?* zu1JdqfGw1mcR{0_I=Qs7umcbu1L&JtgiN-kMqc`}e~5j0cikGL0&llA>_bPo;y6#A z=&==0Sj7G2(!?jw_3&EE4O7rDgF8q-#IERhRHQ;w<{=S9BqSM=Xf`I4v6LiAL>ZHk zAwn6-R8eL!L^6dkCsQ)-^H!~=o_Ftet-asl`2GHQj*hkXetkaoeNE?eUgu>(XOI4g zewSx&)^2B)HD_Aa&Zu3{EjwL#bDv#xTux1EO;@_J#zWhUelC4tLCPkCZ(d=HG5zD zjLF`*x_t&Hd_Do*wX4agTj$-kLD=t5`6BTJOYL%O?ant|+@Rn(Gquv7+fdeCYne&y z{%5pNli}do^!`N$?aY+P%+3I{MDe3aDVyKk+bi6%x!7tbQF1vI?RO^AoZ+rFEwoDO z^ZUd(UB-^=?%h5Raqi3QaSAlz?{nG|Fn;40vTM#i_w~gh<(w;WPd^E^8BM;b=xtOo zy4Tt0=~Id=zoVI+KKwH~kAgP$3GrHZ#N(Tqa1LK@D7W?pf}CP3FWpTjp~`9UX;kBO z7q#clHXFQqkQ;88!!7bu(&ai&n0}mFa-!a3*L6lNcM2-nfBnaXC8}W}^7hA9E;ix! z3%n?(wCJtfM0=N5pQmQ9-jMJ7X}^yHZGk4*Uqhvf8M*)dT?RIf7^6X~%g}z8WOQPN z_|apizqUZ#8T3!qW4qf~^%{n-iegzR#$FPtLMN9bm23y^QnA=$WhwL*1d-+iU=TwM zn+y^?2T3>b{D^#@!mxe3kMzR^9p88Xt8l3^iP3BTX{|QDs%k&5%OLrQD&2OTy?m6y zh5OLoJjx=WIhX1$^2**>*jJ;lLf2;f*zKgzmx-tQmrArz^tEmt%ZocjxNDfs(lUu| zKO&7ul>we8<8N1^A8CJCZ@XG~zr;E!+5%-RAGhZn4mnYB9XVJnsujt<-=td?oZsvo ztvTa(;hx<_8S#BDQ5qkte{|@S#!y`JrC$0sXku8hMCpw0xwGxmaTiI$yY4RO{TRO6_I%euZ~qaMT+{ zdpFmz>hWtIoLOo!yBASXGgKLI^^ZyVy2)RV%A<>q4f#%aq)a38-94Tc*-qI`79Az6$&`PCc@wiC;*m&)EDv|S# zXdGuQMat=x88knf1gj-+(feM5X=A5PEPZpaR#|8#gr4iMa<3TMRMi#fma7h&?aS_* zyK_+&mfCWLL}q#lg3cHd=~zu4eim}xsG;>ff7g|(4ISDS>a6`5b?rbT)J7s)H$8ON zD-TfQX<0{{)pE?eO$}^At4E-bLv$6t=gz+lByZWxdY9+#i->=%q}$tXcyZbIF$tT+ z?3C0#>+)xcqqXd~s2RSl#}#I}-puH|7gw0E-R>wg@j^fTVc`){eh!+xhOQyULesoi%-hFwZMLV^69l%e;B|@nn6T#XzvJ_A9km42Tzf z-xwO~a<%T=`YvWk;r;6*mf}L`k_(k(?A>>gONg35U{k(xVHo-5_VxLboac?<0wlAb zK#?e1re*Tg`J)%xEq!@Qbik|APZ>yGhw@1gAQaDKzN;Cl4Bf|ek5Efp)O4Ncs&7#2 zA|1+ppyTP+1#M$<8F`;dqJiJ~@NKj*J<;BNT1wU`AUmBcr0<5X48@(D&##+Y3s8PY z%ZHB={K(|~@x&&$qp(pht5x2y@#cJQzxpm?!}IsH_@JpEA@f`eqV^vR;?nrs*dS1m}C&!?`B|w=pejy-c4K)L;0Uqk)p-C04T&g21YD9fIk*W)L z|5bYmjlpYI&ZXGta=C&Q81WbMl1(gZCWtC2q&oVtTHR(xB5ko(TfxPr>XYF^-dyI+O!MP7WQ#FAX**E|gg|X`Y&(3(x!3c*6GOV*Z8!QUYR@Ce?Mna=Gq} zFSXxbxw|o_l;*d}-q4jke9vYIMD2ELQC3t~GY{`}4i+7t` zkkVjXd4MWWPo4TV^MN6Gn9=g3G)S)bvf*`E`wCjFsOJ>p{AVh;hIp@YQoc**?7Va1 z4nNpyBeo^pf7=}c?QZwJnJB&<__&v>ra5#5A+wC$$qGqkamcIE=)w{zCkl?(Is2;{ zUJVDdU_b_QZ>M08?44z0SUR#rBQ#n%Yd7`{_U3-u#UI}3lqYi5x@5C*#jDy_EBMV_ zKa8*H*d>MyRK@s;-43(tvam`$_c6)uO(ZH|Lb=Y@PyTz`0!i>R#av^3!V;b(09knA zW&$OT35*ITH7_vVLE)i}^Gma&CZ~6gK0Lu{Mz>WVv{5LlbGa5x*#;euN*7$Bpz6UpEL%i9%7XJV{4zV9H>a;$VOf4B zD6IxtrFWr4cON}pTUe#R+yH!EDmwTHF1d{7&}xC72GuLCT@$p-Z?8E<)`h=#G)E1f@<8cIm7*TM{rw=jooI>JU=z* zdfHMO`WWD82%QeC`7#G{3!(cvHJJZb;d0e|DRy#AdWrglHVg_1XDPpE_C{=PSK*Sx zvlJI^<=sk>gU_D=|40r3De9-T@2Xx#MSBsRk0o7XhL^#_W7I6}Tz|^tCi|K_qGko& z2Nw2^8sE-jkv4gHJm_Ji4HkCS+_c2KBa%_6V8u#)nNKS~26@=N-#8uDpctZ?{z zJ-kKf8d>*?oUS*HB58T{(^{WZuKevUQ>LKIp*|4#s#q_nvML<%Qu|NEK%?eOe2SFY z?D_W)<=IIA82&nxZ@rk9b9kb%DEfjvFSe(RoSv*qe(P01MB9#GNt)5dy*691B@~25 ze$1f-_teB1coNNAGJFs9(nBngddm!|GYF(on^C*-DYC!-;R>5#E1NJ$I%I)t$O5fY zm(fCLwohDQfk zY><9>hbl17IoRbeWaul%&~jbZMDWP+K=N9k4J!`I#yr3t0HcRp4n=$z0FoDod!Bhp zLz;Ryf4=h(hmzvtx#^Wg-G)OC7i}RU5eJTg&P_yxJja)rsjPEm@EIDoF#TFwTnUzW zn^-38FU!=q+fO(eU%rwpkMu&BT!WOgY#XtLqki@r)^D1lJ163 z0Mifp&erXD1@?{kz?z+gu5{s-8R1>+bajkbM1@b_;=hT(%&x;t0?kYYB-HMQHKGYo zL{OV_w3co`PdV5r8>B`pxHrB^?Y?=-_7L=Wa*65mZ@<~e*5(}f9`EuQ?PKGffc5Q4 zT#l5w8+B)5Yd(**+?D`4?&(J2&ngq4jFlG#;j)Nv4wTh|N)#~;K(Nmo?3ajf`s0gY z4RK-u$0oN^P~N1D{+7TnILF>wH*fm&R{G6od@CguTHs- zpnvsFRzY_~_+OON!$`nx%St0ponC3uZO9mB<(>2OCH)alWg`m6{6H@UQCn_5mKVh# z_VxnnH9faZapMWDmDCJv1$QLf-LFuESy2bqjhcOkuLoIT7bJ=T$0>-&njto&e`}RK z)5`~AFZwWRdhfT&iR3@i+@@y4*xvN-L{5{9nydOb`#COmg)O*;Z!_(5s682g=`i?= zk_-Q{T64cmWjGjmTW_Zqo;Fc$okh@QZqs!4nsY~^H##ICYy!R(e_9Et`|K|k8Dx_o z5o?PWkHk{IF}VaYD-Fq*BBvCwmqR3UPg5BRi%8>e*lXXPLhnXy{&JZoFPEu1+$H6%jfZJF>)l+Lw7Ly9$GIHpSJrr-~t=RUcUIQ8MMMD0#c;!t_qd${UOd`Jwn_ap8l!4E&VFc`uG22hHf! zb-7K#vhC_NG#tyEM{a4q=!dUVtPM@JB~={L*b()tbdmO9bXa^Hce6?9)t>@yF0~(_ zQlF17Etaeh$)9zJlym6>e-yi>VPa6-(}(`uy0vT9ev>=yR;QUXfDD3V>*rCcM;na} z$Y$yvb}7HA%O(+px8X zQSx6$L_u|k-a2OuiFfV_dvQoVf*`YYeoxL8EoNyqqPG}-)Jz8TRH8&Lz?7ia*+N=<=#{f34i|c=l|i z?D%Lh$KGY?qwZXX8~>WPcFc^Ho{~D1qyWsc-5U_5GMkLf4;7MK;Ll${c}kz=N!?cE z<7CZ3S){xPVsX$J(^&DFaaK|~G?_K@7O_$juo64gvHfn#c!lJ>DqeZV+TXR?`UUDV z-hu*dMckJUsNnnIP~;_9do?e#=7s`bfC45p2zY zR+7wCr&(&g#~9kobM$44V27Tej*EM*j0tWpws`oSe(&)?TmxW9%!te0@Q==Zl_DHOr5E-z6Z$EyyZ=+iX~w7*C}rOayW zths0N)$loxFsi{qCdCy$jqY6gg=KQr;pZJ2h4nh1c~cBr_e)kJAr~R&eRRrGbg(XA zSFWJfI)d^ z7apwqfNnYRinn>k^HGp5Sa?nUY^S)zVs1`qh9ZD6g`LEu!fZZ$!7kt%h#Qvn2e77F z%gQ$%^b4}F@15Jkwz_!*F=R|#r|b$@^avS7Y}S>)b$Eb~vys0H300q_2iErJ5Xoos ztB>!$@g4mM{la23E6mmEPGN?m6gCt7H2KZ~c!9?)do#h6@y$cW)u6?>Z`1Sd&emzd z>~M8!c`JRF|17V6B>diK?iGa<6V@hSmaHkzfec_|2?n`3z7qn0H0K<&C-t*P-#AXFn;c+eTaNn6XG>`$k6D zovo#Ip0#q|C5~pB5&%>EP;fTtq{R+Q1#AQ<%+b-wu#$?_9ISEUE*Z&yZ%Ny-bf+1?G$d zNY()CS9Figi|-Ib=-T+L?LpN9j_cEd(9;11r?U!mb9LXI)9s#p{A#IPUBu4!c+4`o ze-v>NwA}~#!_@A5PyS1c#9WSVtomFXB`c z`b<;SvVya+8HAu-JG(gE%HgH`c!D=JaHShSJ>;l{GF~IzAkONbN0MiM?7_MmDd{mk@8}(7XLD{^E}& zjVjC3?bkT2?BDIWRdqQxkGuO?Sdr3CE9yFp)hPRihR=JYlPm#~iox(%MF<)(Fark$ zGU!F<{6`kn5r_p7oOKUSuEBRn5pm2_iVgwj=wP!{EO6SWeZR-D+6N+R%hg=q77WzHfkLD*e5)1v|7!!@mQ>rhQ{Tk ztBBz0cy?sys@%ERb7-C1KMyh6(|0VNIALvQ3>0i&T{k&=!WNb6u_U(DhV+73WA|ma z)Bw0t)yMW%ak}5(^u8@F`a;XK7r914w3do3*Kl8C^LH@34@_W;A(VS7s53q2SH2#E zI|xsU%9><7s4F1bYCaN4)dicX*eR94l_zEC-@vGP3!j`iTz<5CISOhGIp`j(MSYgx zb3I`hH^iF=Dl2$-we!&!V4_zy^gFsj@ONU+ZEgdYMz^r1rBq9070baCo2{%tgAh9` zo)4M_oWa_DG`9&XUgQ>)`*=O7AWh#K zf|oF4Q6d!kL@K8qgQS<;vpvn@`ATQ4?jKegPB{F|o87}4pT1UX(6@03qK`%A4K0lN z;u!hNZ*Tq+k#$~NkVSH*niE2dn^3WtxKG_2s$^v3anD~vT+lyoBueTsrZ(r+>@ZOt zYIPRV9MP?5zV!GuiJb9uMP^-)CccG^L8I6xGy!5TeS*98?A0&8tiG8~fH&3 z7J|-xkL-a3mVz=V+#7nLv=JyZKiKs_xZV2YkY5l&4oLho~76YG4>EW%51_T6Nki|+k; zc#Tb4z(Qu1c1S#tLKHtSWjpZgXSw>kMZV~g`Y6}dj(WC~jpwFYTq&|TGkJYV_M&kWIA)tO9t_|4$U&bk4SP|*m zyIvBwzHEoGR?|5|aa)w*CAd?)k}Z&4R0SnQX0XeAjn{>)?T--yx!m11<&RO_#C5V~ zG|j1At6hNLw2R5qMdRRWc41`xsGCEj#c$j+5#N0zV{>}d1i+#D$Y#HF6Vxb9CoX_L zWpr7BX1J8D(6eqH&8bCoAy-xhn#)Xa=M9z7YjL|Pq*L^%+JY!d0ummYhFbJ<6z>3ipSMqk-{XEsgoOtj;1b;p~R4 zHwipNG;D@kC}b;DKSI!qADbhhW}Xw>WHJNnSwkd4ESPt%L2d85VsmucY{OelkJQh1 zCGTylP*pch_TeqkLWY_|r7>8%KBi%UXxW2_9bKW7eG(n>Kmz8FR-5p1n(A*vf+Fnv zaB;-cJ`(gcKN~<3~zlvk#lezW?F^ygVaRQOGDVCGe|oMHZb{Ir+KbVv%I!Htws1 z91Hq%hZ65eET0%lK8?oTPs`nO7kr*l&_4$B)nutx$~r%J+6rwjvVA6Eiq>q={}~la zu=?W7=@CPm+NKWQKciulHkS`cK{P9~6l>812u7tmR>Jm{=ejwG4wjKAyl_B2(v|t_ zB}CA)eQcH}nX?($-v}3_3lEh#*C8t;J|fb>>uqAcE=g}ldmRs4`M&%fSt;YI;VP21 zGFN{n4&mvtDd)5yg?a@N3<-{jQ!maWnQT8h)zM$Et80Iv-h5)9kdH66yqU%^m|`=_ zHaK0dS?XPagvHv-kfLNaHS}Pt8Xj-Q6vbisMqe>f`#Q6&4ZbPiKD9%@M)A9To0RzZ z!)K_&u6A&K(mIeFUE9?_vIbYYISUmX*r4*-f5jyZ!5xD=|;C=6xyElPF|b?P2pvFk%?!I zlID4nng7oUZUzey3M>nl4>7>dZotsCl6LrYYZ%%^O^QqYfBZa`X^i`o`l-`&RM=ur z)WeyuJ?r-66uJW!OmRoHnDQ&(@l^p$k=GKrf?o}8IwNK+zLCL_U>222>$5&`B=EKw za&`&$zjS7~FZYJvl>lqy#H68sJuNKgu$v^;IDC8AgsDW zfo0PgQFCvycpEVGCd{lqKH2bz zOFoa>a~J9U*?wb}{{1~7yvI?kGa8&5t*xT?Od!pL+vZc>S2UMzoo#VaAq&l29L5pX1CehSM2=%8=>8Uo>cjg-?n6B! z=}kyWOQ}rR8F!-cAve`8Miv>5UJh(Kql@>Sq-TSu@C%mFsMlacO; zxv3a#d1#O6&36fpeY@e_%a&U^FI$EhXJo)!^@g40j)UZVKchtwY5X8Mx8jVnXkPs* z*W@=f9UD7y{Y@;-oR=Tho1V-nXBxOTDXC6Hkgo?+VVbYEY(8@F)A+R4NS=J;i96q9 zcICbA-(>b~gO%~Kr|a@&#Vmjl7LH)6FCV zC6v^nq;bVKk5B5&oL=Op^+rao}H@peNi{8GpbjKIGYC2YS7o*b!WQe7s~s zauLLPkAR8c)FJaJP`i`BFn|hZL+Lq+qSyua>+fFMXNjdvwvfD*{ji6T}E4$IZV|Kug*YKvp)iI z(Sd2#1dm1-ml<8_1CQf6*g%#~^a^W`J90E|(wj#=>n?~HD=D--1~`WH0FuB@SUWI# zrQKZRhMloF-nqLz!tll5N`Yj%X1|ikF>8kUjyQ>#rt$T)lHOArT&J5pdpo$11dM}J zs*ZGz<=%)1Pec#f&Ayz7JYd3na(IL<$0BX~sD5&GXwQ>HXtQtX07)YH3KM?`S)8li z2QJkuU|DNB$~Q$wFE&MzLgBsni|6!f3CW9S-LWHUR{znu`|pwz5j8GvlB857O1?OX z*eFao*+Ih5%3Q@H?XHkJj8Oes+RY%Mn;+BXB2IJnmXMJk8i*Cpg`iVShVgE*YB98k z*S%q-yK%;w*RSX(nd&Y9`fW9Lx-4Wf>23RN!(z3-v3cE@=($K{cE_u=5YX=Tzv7XL}U;M#BzKE|Boqbn-$82BUhQy089_hOK4WNIf&CO!-!xh2kq;0|@B6S`< zDp}93nXm}yMQ(4wPS{k#8hY)A+XQ2?(ur5`ll^u;&)DXed9^bOn|3>@30c4rHIVo1 zmyx=y)oJLTr5Q*Ln+;JR`>4|ij!$QjKB%g(9k)Lx%UL^?=~FZ45~&z=;aT3khmjwq ztAx;@^1Xy(`XU6Lg${ z!Kvd)_->DMtP=oMX+t5%Jm|#-=yDplTpG>n@Vg0bQYk1P0%5$w%1G3Rx;m7@is>K_ z95}qmNXDQr*>46?;zih~6`$Y53SsO7n!9z>df_Rrf{rN%{@AE# z9<3jSXSKX*HWY5Q9B#NzuiY6c4gi{UEB~1f>IakU)A&)TdmAGykY$#h6I0RH z&n)ThD#t`g@`6i9mJU6feQOz5pLJSAw`k1zdVReViWDELWT!y~AyxQtGViTF&}a{#gNf(^_Q6br}08ckkMDW$d#*LBgDw ze4Lp)CzHVux2@W~Xd?Kp$BS4~DK;N&V41jfXdh|q*b=9*`QIkv`RX2uKBTG>>!P}c zYU=+s85h;%dPbC})=z^G)QP}bw)+Hh;E0;TW4en81IPe^fk&7u{A*?4TLcZ=AewIp0j{ZOW>IM zUC91mvOH6lLgs1Q&^30ko!B98Y%U4yws)4MAR7hRG-0aTK`?h!I?PcKdStDUke*V3fikF`hPSrGJUuak8D*5E6*!0@jLl9m;*l93Pe7d}|t< z<06pPc)`z2rK{^nzny8^b8A66IwnVH!df~2S1|JP`h=-SUzU_2x8?!4HNCTM&ocjh zYg)!_`u>AkQ`zO}^}T27jxxVnV?NV&Hnq9gU)f;)B{C6X6B0}Fn%x>hT$62pj(g@RUtoj{<%Qpu*IW@-l_hAJ zi-u+bInhoScbViW(xA}to(|O3xB<50`;l5*uLhviR)euVHxN}sEp5)$sj^J(KdgQV z^a$r0_G9OCnT@V>K>R4}L)^=+{xZYfx?wW~Oqylhc34GaTfnGdJ}uOpDF+gp7EPUY zs9p8fS&fq~U+@zvnvy#mb|E*fy{__<@zG!qG<|OW5e0dKe9T?C3)p#CxJ31W>#eF2k`h$3jtn4Bxhrc^o2^4< z`j1BFKVtT-{5>glTU)>y;2C@jn01$!X9|kiCYnrn_VFc*Yp7^rNaUdZJzXSqLX+xPu^ZWd?s+@a!d zGx|Wo*w?fRY|Bpc+OT=!j$?!)QFbH?)4#bwS3D*R%yZC%dqme>&a)ymIX?Hq zNcBBU6GrFEpVE+ZbMfE>pSkRHiZHAEWLUQ=P58KfyHveoy!=7OoYhMwon`+2y99gk z;~JdwJ3hT~c~HLaHGmr%wzgUGCNZV+;J7SD4^8Oz+L2VurV&5tWW^vM`y?N=1fH6S zZMq@T$f2Ij$vBT5%>v`8KQf#yoX8{EBKi6-pvygk?qKn4%ZT-mA8>4ANj9%LQ<_tQ?C5Q)x{@N^7 zddelbj1xfUK1?ZtxfgQS?*@=t0~^%R2jphIA1)rhqfT=U+nI~e_CNL}=`}&a4djD` z)ZaciragfePZepRb>?WhGfU9gH$ahRa9-#5<8WRygBPmZa>YnhmVXWBbvkhRsf}IU z+U`EX490UfFsTZz!KqA}rIWKhdh!`gca2U1X|Y&Hyf>jdJAPhkqGMcXA~DC*r)IBR zIg>rvvsJpx|JGPZKj%%8xrw^mstK^=LWjynddmfR&mKPk;^fT0m}^`u2mhTd$J#SO zJ-=B-vlToj*XYy>)nOE%xhzw4NU0G z7HH?k<)2*+Jr(w^at|FVjG_rX{F0%~nR`iHnOyjnjEgqmeGWRtNta)F6ECb$QNq_f zwZRy9*+mCnS;--*@U9eOfNFKz4nD8tG@q9(D;(`|HAZU2UhGW{ROaV_I`!;|YH1?7 zoYWowW#6}NU%SaJC9!|3d^Zu04&iAgyzP=4TdbN|?te&q|Dc_x!YXK{Iz_D&xd!*_ZB+9Qr z3%uyG1D1Ofk=(5>$_{^yoEH?@d z0Z>e6O`3aL)mD(y=hG)=iA0GD&jvd>IbXZ15q3Ror}%st^=H{qSmxjPa&}mr_3Zd% zvq?L7VOQsJUvs5WP+B7hr4_A(``lg?Mv8CC(kSR}TZ%g1YrGNEcs#ov(^7LGKvF0@ z!~Yfyw3@~1_Jx1=Iwiig2lKQx0ZC5Mn`B`8qAB_pYNNk-fWHB2IEYL(ks;^SIIh6z z^+w&(KXGU&VM7K`gSxD8#%@i+!h+FLXx_XlzF|Ukyor~rk3JdNVv73@plp@qROj*Y zQv(^Lx_U-IX=`+K65s~sfxkR?B&m>NeiJqYlj#YAZnw&6=kB{djsm%Ls~~G+BU#yl zRd3e5a~Q0+0c7s|0MczfEWt`~6*3AjgUX1g<5$x+a%Ya2wmaXyDr>{zu8qPPRpWXA*77d~WCZt1 zze_ONWe2|1JX0}EJo%x4-E5caf*yFj?@sc|&;8)FTR)kBt;{xpir8vFc#~?;fZ^U{ zL}0YC`O zjU@K~M=B{~a$_p!USC5@1GUZEP;@&d@?Ji0gZTF_CO}r_9xOQ4nwvw!+G`2~jVQ7KRqeMqll);Ze;60gF!hRNjLB?3 zz%hE)O8VcBj5xHD`-{#!k@iM(6OW)5{Ek~gdSHtaxX?LEYmS>dtD2Z^>yU}dTpf>b zdk$SwBbI}jtzn-(K0DJFgM#jx5J`*1vdm)#kTD1F`pZgEK#&&a@r$SxPKrg{EyA6@7=77^0o&MMjl!S0_m%x#9Cpv$bmT%uswasMK zV28atx9|V6+sktE{EiIN-_37YN!4|xI|Up}W*{2zA&s2$9;Rs5F6`T7g}@krXyuqb z3gXl6-il!jWs+ORO?nqiz}5 z9q|fv7+_ilN#1No>X!>Og&$9Y?nRt#!mX(oA5}yZrGeP=UmZ}*so*+C%rO(NGJ6kR z@H)hd@`>@Nj^j|v-)Qu#R%$;|(nQZs8Xxc@F<5kuQRzK|w)E(8Jca(fo!9z}!coVz zkHm)A`ZVWW$E=CNKmcHb4=zOC5L$u*%(=Q;bA&bBd>ZRjZQmaW)|U@84tZHHRVomWdrC+NG}-uwJBB4ItV+pDT`rqP!*ID06ItYW4) zImvfFHg?L7UQe}|2_&Gjiwu~)>ja0y%=RCSj}QBYT7OVg-2DJdNMY|Lqv`{kq3B4( z!++RL87pymd$vC`W0h*nIhUd9J_!b75P)x8A=2cz>GZuVka==`5|>mpOUREhueJ8MCX^QrQK_V`d8T zB&FYnIv%Cm-!}Ffn@h}B33q2sjt>f#@IOT_a1YVUIvYkUFSgfvmK zBI-X&6HCv3>8jIAet*dI3F%RX{2Zyl%W^#SF7fu*gXLq7zJhMz_S%}9XdlO!!A|l2 zI(J-eHEpY?M9Yv$PRm2uCK9#RzY|`e;C>b zwR(1oo^t$Uz+wD-$k?v~l@sQ5D%@y4iyU1Qx4yqaGel(j?2x}(XSn)c?o<*d8k^r* zlb?T)(kgtEbCMKhO-Rqa5%Go_jJ!Jo-xq@VD+F96^+PtM6{1~(bB%0^*J8+oc}fph z&`qdB9i*wQ>pEH^uG=t!!m#waQI$=c4MA+aZLSTuyo5dAY#6ER5&7QCPT`JivJ5|S z>ty4TUEs7Fn{#c;_YlP>Vc5(=Dho38zmt2y(5qtO!!@bDxB$&%J9JNe17rS)zZg{c zd(gmc`CexzH(IqrnZ;i76afJ*ER`ynd^`G*8L6bPas3m*FlnNbV4s3a{dIkW&j`?TX!et?lN+bJ_n(``{2sXe)4Hp zz?9?Nz{u${z0s4*1O<^BE%u&OlPK7@}~eyBv_)k>;JRAzND2 z1l;UKBrA{oljW?QAtj8%U{b||_~F+}B(dMDKQGSnL;TnD;8UpHs-GOI@iii4m?WAi zb`JM^P9^Xxi+es=TIv@sQg0P0BRPUH#Ox_=r1l?C8eXoI9~hi!5$6> z3+~#vX8W5lviTQm;r_$>lZ}mPCG6f4P?!uUaBMsI_=8nzt`T~I0zVzGWdm6qQ;o%K z8XoPFLAp<4&Jsm^L;vsA zZe>On9;DjddAj+6|epNX40J!?Ar@HJaBvr_&(5bF_eo`gFQjjosWHu20a0*$#9oI3^(f!TP)?~DBa=?pxi%UZn@et-Zv;TN*=_IXEsXsQw-sGj~G zsGctOe2-_MDVae&$HM#l+f?HlCTTR&{uLkv^oI7-iPv_0s0~%D-Qz@X3F zS$&{L(ev7_j85@&SGbmd28JWG<%_1IN+DyiA8Hv^fRs#7o8AB8fIjz&p(e=`OQ)I; z9Z@J`KZ^-4eXoofdancu-OYWNvQ;NEVbbzHf}v`58&*{u+QJEbG{wWA|J8Z&cI+n(F&ddv3IwoA5u} z-cy`2<)&v4yJU2ub;UZb>nM9k#AaIXZ;6lr>+{E~e&lLgv!t{1-xG?Fz_}q;BZ3Ox z>?`koc{+P&XV%9{96rVvqJVTkvMQ9bjWnabG(Rsz6+-edyBy7)OYwVOU}8knCos6~ zwvW}vmtO2N`9!-at2_E?f3wa~;#vjM{U}=oZ6cWC8 zf$Y#zC}+v|u0?b5PfHCwUjyzG!`BUMIwCTl4~LsQD9UQpe=$oA)`>LWBWnbb(14Gx z>iZa#^hP&qd-M2EJ7+CAax+bzFFX94Zv7o$ppzvXxn*w^XGZU={k#+K!s*j>ML*{+ zWt2yOX_dm02|>6hwdDYzq-7Ic{7v*^H7g`KmP~OB&SAc$K_ulmN9CuM$R1*3vH zlQ7aWXL{@0yJu&;S9|agzTo-~zAS&FW2#9M^?AkRvb-v!5d%hmw7e1^pMTK={`g@L z&HUOvB0JPL8TJ{a(XAn;3Y0H;tIvPkK7nRz4DbL4MMP^2c2RTH7V0?=i}u}Bq?sE0 znWZ(z6PiynooQ@38Ml>3f|oc;RzPaK**Ev8QD+TkqhY#A&jH_h?2*v*HYm(SyDiLR z^_g0&XrKJEUz+scahE+W2jF)E{EO0tE`04RaK^i??a?O$9-X0~Uun7dJ2|h{L49q# zcG-j}f>}4Yj)tC7R?i2v5p4h>+u_vV2EU$|b-gqmeI(nFa&aY&VxB?+uZ$Yk#g|YTMj1r85hG5Z#POm{t zgv&SmzH$*FlVYqqb}5hqFH9BiqCYTiC2FT zpO*{5-W8cDA7h;88|*zrlMz*a{v7VqW)_Aj6H~w|@3?UM_vcJkknZ%q5;3pIak&(AJ0z!82Y!hz*+9(sGb;j}AdnRvu z7q(-GFV?s41;}xaqA$%)pVxvKuG%@MEL-fj9d$K*LBU zzd`^$G$8QQYh)dV7E7=!_aAOa(LTv6x$MOLDl+_PN+<{aoHhBgfDd}_#7}M`rigkj zVn#z0Qkhv;M3ZKYjUTc^!Qp?vx#Zgq*r4D+ga-x-%7Eq^j^KKU?kCB9iplUI8oU8J zmP#u98eYse`JRWc>~k;hpfLTr2BpPR%8bH_9=rt~GL=GYz7)yR6$WcTfz`?>cCjeJ zBD~{@xkx!~0-yf9{jen_9HW6xFs4W{6ARw%KK6bA>mHcHKGaI86Fab)b`0abc8nN9 z58kMsszlhhnC*FZvL#zm?UrKKEI)^AC#xyQyxc*UQ*ah1KFIpB9xqAHbqePRzn=U z%D(UoX-52dVviN*4zGC?!vGbuPGoduX5_NGzzMSogToqoK)`mjVM`B)91J4*@Ju(; zN*fqNFnU>%cwQ7!{UYe)zYjAMT~4izHj$|GD>4PZ1pFU8_u+=a{>8rRVz#W}nnMnK z@Af><2p2>?HZE*Th(c6VTN=Hl|D38ZIe{M2!C3z`M%6$(Ms#(#=^u!XM*$tGi-uY; zYk&#R0kQi-7YZ|*id~bR{fd739mo##%{=z%0a3sc*(u%b09RtG^2nxr+3q!DX(OAq zOrL+VnJjJ2FCH-uApRc5!95hqAHoNDG{L8pA>pn1F?`3u%0IEGY?DiJ0gIFg=I(Wy zrio-dQ8G+6MMC(Aw3=4SIv2fIw5I>RA8DNbQ&sQaL90b89DnJ;quYH@d7cb9kIqMPOTN@5QEoy{mv-lXK1I_x9M2R_`+?!i7Ap4Bj~3>eggcK zap6%gvb7Mgl-E|;kV8);fH3!eeHS~irxJ2LL^w^**7%(R8iFMV_(aDmW#h(jrE&SU z9ZfqBX*!XWRnXax)(RB#Bra>5M8xOv0E25c;VSGr4kVCEm$>e^!4k9#; zuFMaFT;LC{!=r3JUJZ^u*LA!CM;Q$3X}pOpl1wA>JPJ_^;0x0IHnn@kkBs*tD87RXrD;3)R1+~s9geFB&*c4!oC^hgg!cw z`}x+K|LVc%)OlmG_jLy1b#7pKb1!DEBb46~t?Mk?wZIJ->@aI!_QPeX;a#jFB3{3F zJB5AJxB-FcVgTl=_TUCQJL2m|P~l4LfZJzxp7JBoW5sKE2_E{>wQ(KA9iZ8?+YO&i z22c;AjuMvsGFT=HF4yS0eBzI^a%dI z83xsD$6!I-S)X?rqyOWBttPc@{9)Ff7*Jb1OLMgf88&SZY+<<`!W~<{i8NRG5uDyXdksG$G7Ugz2k<%e&>;Lwcv^t+fOWgDWGSjDw;T zukR)^QYQ8ZR16Qi70CKc?ELAz7hC7PQ2ILt&;Lu+hoaK$X27x6xQovgf0a?r@MRPR zkksb1n-rJjj8Rotl3C__zr8N%P5C4DqLh0u=KGTPdbL}w#Fo`5V?8^)$b;YH=_(^! z7z)Zf61rK;-EiXMa-_saq94K4hN*@3p=^&}Wm4qC`>)!V5BHh82QC5{-gThj2Z*3p z57q(eBpJTXJ3x(mj8{Rpotq4bJArhX<|hGEvZp@Tgpv2-Ui@tet{$Z4fHk#gD}T04 zxzXa}`i!i)KxX?z6jZk;x;XxeWLpgF*J<(fw=KfpgF&?c@S~_;NO!~ z$Uwm3JrS6}rxO*tL&eo3F}-e(AE%HN3WImq+ZpzLp0mJ!fqMFX2%o{*cp-^m`~2D% z^MB{@0^38`M!aG0=_y_n^@;ktq<-)M^1K$5(R8;IT8sy&xEmMj@B{xd2>~yW7o1>i z3~=Zcs7jDb@VZG9$RncXzV#@5y=}9*pQ>2+r$b zkO?5{s~1i{?;sav|9KvArv^Nu_#+@50ypIqp zp~UJn3s+#^&3BdbyWeiSiQ`8aNeF|b`W?WQV|~za{=V@@3Q7UWrmmDhnO}W($D;pF zZ@d6b+|(%vCq9J}*Z*C3B19!vZf%?)mM{ywikXQNaXdA2%inee zp41b^jsG=+@Zx2H2p)^mNl2&n(Dv-fe~_4(#UbPUp$ z2;xstLO9>ETsef6PY0>Z#dQ&f{+u#hOj4U4@PPt74E^Yhb)HIGDa6oE_OfSgCx$L? zdkGER&Ci$S;Z6))M4%aleiVQE-M7`7@dvc+&X04?5_CvPC$v&4H)hGR%6oXpZ+TD= z1sRHKMmVMAFl^O%(E+YDf&9sSRVCsVs+8xGIR-|DiG&W+k%d*CYLWxB$NVkygGVp(&6)IgK~)cGKa4 zs|u;O2LXxa#SV%g%{y&j%+7+R{bl>x5hny_l_&9(W`s$!bsX{#?#e{_qKq&v}6S+L>T6|5fHFE z3);RTyLIFCDGTa6Y!jG3T2lgG?Ms7@??Uxa5uJ{@E<)WpJJ_PXeJf!=PITUMq*2?F z!tx#Zou6)~tiA#9uYJJ3kIuzXC4nF@L)zpG&<(8w@#ZJ>LJ`r3Iy)YZ7l@sD;d` zw7a%!+sW0sXrhjyE-!_wM7Bs=LQPqNcOWAB%6Az`KZ({V@v}<*m~!76f^>=A?l3vk zs1*s?y6nPvY_Ia5QN1A>&wtjQ?1UpTFLhnX(xd%D&R{{SYA}g8rel zgD(8}#c2H(RA_8v6P+vlsf-i#8pfEb^_)7QVn;(1Z!=n)6B046RnOH|m%tB1JJ2{PTmn_oYIaeo z&1|TcKQHEy`NK9SJochs;-K3QPNJL#(&x1zyQNTryOWs0z}L*huz*|#uEiIBITCWt z>cp|u6fm$tje)?o>tPHY-xmk!_J{F6S)PrNWDx5hV`FOTz|KKsfuFe@q`4Eu`WcsD zJ|A@_;<*-Vv{IoR&UC+F``^;fL5R2}0HY>KRSV_xiSGJkZ z46|?Sf4Fx$?8S60cIB?t&9OQm#xD}O@we*I2b zP83jzD4=+$b)G3vczELN2!!~p`U6p+q~3yqPkvp@YfsIf>Wh?m`OV`CecV}zpXJ#@ zeZ_qP-pF&aXvW{}3h;;tEuNZ+D8{ONkX|Z%!nlQsmQMl7W0boryz$E17g9~1i`gDP zi$W4f_lEhx^(PhE>mj@m0LYSl{q67-!NB?c+A-*HWwn&e$)(mm`Qf+4BFG5kkzga% z&;yJ4R(hQ8^YHmhW*5-={xF%0d{c5k6L|h*;nMY!gSa(i(%#VHR}oY2Tgl<^lQ0n> z79l;Dt$k?;`*M5=u*+pA;3ZmV{X-)D@lMPH5phD+U3yGTdJ8XbNtK{Y-4qBa| zt(BK4wP*!{>LmnL(cv-yWbq&#Th7?-ETHAnCGVu{r?dDMS1feFG$zdU-P`%syh7pY z(f`HUo5xeRM{mQ~hccwhiUu-;N`pDE8_1B7Ohpn+BvTZXVOKgKCDLH7gp5&yh*Kzv z5S0=_h)PJ5@Lb<*NIK{DKJW8+-{+t6*++Z0?{%;58n1ONFhH4Zggbdvpd(bs+lJ{E zuwDywM3E2#OuVbe%DPPqxoC9(%ya26R>7ow>j0f5h!5+xc*@GJ7kW9ZUH^K*w)A)c z)FGpR2d6&O*jKp^PNAPv ze&|i}NL0v%ki;aKCPW?qbZD!xa&y(fI~Uk{1K) z{vJVQ#!yGa^}lNosa|}7W|W9TxN{)MF~7Y%ykqzQ19-`2PT72YiN={(!Z<5WW)pnH zd%|3XW{kD$f;H@GEC7~RjwzPJ=*6Z6 zg6c(cO$U{qvWCC0A<62|*=l=>bpTe*Mp&~{7$VZ9VUeOqt01~`?~jR;taCuDIl*Vs zgW4g%5q={^71*UhZhk=9ge(~9@;FII$H7hPk|^g%0$CW9DCi<#mnIjfyFgNQcCYzq zR+wuPQHSS^FrK(WC%=}?Jr8OaOgKKH6NXxV+bmeC=IJ~(GRTT)Mq~A3GPz&%6Y|lJ z=T*kepbWwtn}+@MugNkSGVu2@FYOtEUR4IU+FZ0%)0{t4hMgx}MilKdQV+8CY}@zP zW--R#JzsnVJ~QyV$a=ol2Z}rt>RhkK4QbnxZLEONGC56G_Q~`55%3XZ!Xz1{;`DUu z=Nfh3uZd-d6`BQ;Cr9SC;b2_BqC+2xGT`h4;g5j(~}a4iStgsredK)x2BoO3Fi zgg?R}8)CZ>v9suJFD01mk)#r{;t<>Wpb8-iFc$?E*YFCXYqS5@)gIKgw15nIkHr|z z5`w|GVEtVDCiYZm+(Rz3JvqBwYPeDrS@A&rbLW@A(#F~#+IbK5rV^#&BqM}sz6adu zh4aaipNuTfj6f-Xo2=kieu(t!cY$WY?w(_{9r&NguE0HalD5R2P285QjgoP5{7iul z(zmc?JK-^5H##`u8pb`l0dv=av+SKKiswx|MG7g*^derX;H1EhsFEW|HOyA5H6y1n z10LWqFH(J5oN6A^uqI(WGpHW_?<~Mzv3z#Uxr8ruL4YleDV^EU3mB*&wWEhAyVYHm zu=ek8Jk1;ux7zi%M&?Gx)10dft~pLQ>3U)<<1~Y>*6TQ1Ync@b8PvIBkBe8GL(Z#7ZVe-ksLcXapNc1@E_JtM9@$0RduHNte_O((%I!xxJw1obV==Gv z{oKpe_EP`H%p5{`Iuwuj)%qEw!PIP~9P=A1J+Dbnh3-+zBX5VAt`G^3V@NUH`efhG zvF5v9e(u4r57tvyf@?nE!VhdZuFX0qA}KW3+k~Fb8K9?P9X{>(W^q-4d&&D!%+21{ ze9;nr7R`HkSc9qy&kXt#xH5snu*CeDTP#_z4SZ8qV1DoCw<|H|7WuLd9BlJ3MD3(i z^xWEo+3fIJmjw64gk`%4Gg(Pzz0_BGQ7ahD?0Nk4*E2FFYUKBx5?kXng@M9|d!f=_ z46V-}N~y+cnlsC*O3(7C@ z7#^s9nf?ZImyHBZYIg6+DwDR-lTWC(cjeBQuX-$ag%Suctf0Q&MLakC=_{W5OKzd& zQv_&NK;I`!{HcR@NBg)Oru<&P;dlS|3{Bn;uADB~^x;k#1zQcyz={+h(#t8K5_kdG zS0p8vSi!=v@QPQ5e_%!XWekxxvxo$-U z#@PAtK+uCWpyQ>?a%L0hQJ?4q;eJ)f?vd)UIP9*> zI^GI;`~sV2E^Cw-1*DYgfKqbB_?@;gjX;6LH(U--(2(>?xa7QC*x@uP9a`5>U}V^)qC^&+rD6xz%k(_>BPXT`X-AW`V7-*&WhgS&q7vtN z{rGE}gro(Mr*aV$KeD^`>7wbQv(Bu9DFD9yJpWIBg}PawnOOAUq{g{mx_lNO{n4J#1AN2%kJQ?Wlzy~bw*sAr*j(e* zQf^I;Ja0;vs6}RiQTh5|zUWk!2F2ZrTB}D1c6xwv=%(}-^F6pJ-&1k=l z`1xUzXUTILP?*!bh1%d(vwJ9w6=HL*UZ(4cBe%*!X%{QRLAJDQ&f!|eWq{J0DJ#EY zBzCJ*{!IZ|qvm|ko$j4SyU;4TuzS%7gG>3z_dW|i4fosL`;<}5dwC!3BNS?vwps8r zwUjlpF0Q63RB_ydkrzKbaf&Ze@Ul_{av4;T{fkn)#6s=H>lLD^IB%}NhF(X^l8W5Y zY+7;s-v}zq?;w_Qn`@)~%L!5UKreTT2aB%}>cL$vkF?|#r3&4EOWhKpxRYCmDr5zp zyL-{qn6*L=$s5AVwun%Ls>vI|RsZ3RlY|6oHs1Zo7f}yMfknb7Ip596Hm9p76^rCm zl8Shg)nNV_k=;B?zX$d@D>!6tKZWbl*m7s@n{Dp|Q(pjHiAIF-ko#MZ#{2_->1H{=dBit8PPcx+@^ z_iXT@D#M$^ifpHncdo{c`{nik?fu1>J z^uAYKfR+enP;9fO)Q|d*f~B|T(qC+o#vYRsr^gK%UMRsW@IeEuIT4Cn+~5;5g|?0R z%MY{tvlVtgLk(iR>ep9a;orKI-)7yQiF5&8wV;+`SPU*W{IIlA^iqFPic#sXH@o(bqju)h0m* z+EyBWIR@HvIr*N~A)m40f)DGdbhnDTc`*8qZ;p?Q9KVxis@duoQ=W0+5vGJBx|PVH@uk75$_= zbvZ=r+bN&z4UARSAr5(nmnO4`zM3<<45|(lJkKfgMGdmLohMLhFQfA43My{99tUJ1 ztmwFa2~!>|hMrv_?z_Fqz{2G>Yt<2F5PfDQzlCj?gIyx0k-^?}=-TrYf$m>P8lk|} zNnqIGyDNe{OXM6_;&w(L_;Je}&+eee0y469u^3C%CHj; zlu>^nES0ww_wwH?8OOpCR19fa!JGAJgs6FXU?p$zbk14Cc_#2{*6n=@ApQ{vkVc5} zVJrNP8DS7014WD*5-i^5vVr`ME{B8XbrzG2V$>*0BFr;&`H-PWl2=D@HJ1{?5-ARc zRa=>~7CJJ2zRN!&3=s$j0Ifg5#gU*lD zFcx9P22*ICGTtn~l;ZY_n&6@xa0NF5F=?#aCOGa^#H_(}EbXgLxqpA~ll}Vc0274D zPTr+JN(?@jS{Z^Hl;%X>Yt8ZaKD@%m)>qS^Hos1BR1PBGCmjc5$*DU5>t^%#_$=lN zMu>*3-Fr9wviA5Q#J0h^soix&a}T|`YQC_3psueG$0y+w zM~tgXK#OG3R|sAvL0JrQbyC!^V>J)Uxz_2#Z; zS72WB-4*!*+nj;>snI9F*ckyi%trfFl&_IFbxjp9yU=&NQ!77bUSIMBD}@Sn40T`H z2RMzE#BJBE#{SQT^8|rBP7rJbbHrI_>jh`CaJVIMPailnRSl%RJNRR*%>tP=X!?r2 z)ylwQX+PVwPW$(}hC{vWHCVs=gy2~s1QNAQeuI$SdkTKo4{>L*`2_+KCh!)+dAVP3 z8hL3LY!fFtu#P;(rop#XFgocEEF#EB)n<4mVz_%7Bit%2gd2+3qHp(rQD3rQNJHwm z_&y4w&AR;FUH+o4Wm%%l@T!FS)#)#9?d=&WZ8jF8vao9i!*!Zrn7QZzY))2-Xr&)` zRF#q7=ZdFGe=FuYlB(XjUT7`_h$=tCv5RD0VajS5b`F_TvYeT>N;z<*n@QI!W#@fn zM?Tyj!Pv+hQjajAFcGf^c#6REe!vjUfDviMsvu6u$ufv~N>pgNo#KDU2FEynB#IhX>|uP=PHE$<@+Vhqr&FpWmI7)3Ld&_`ln#s+s$lb> z82ce$;r)Q9Z}wld|3%^}5E$zyCzp*J9@xBjUJt{`W`%=K(Sk*WKp5-iw;V3>c{RSui;qdA9OD9Iyq4zBQj$F$%zK$FC-l%hIig~_nuj-wdS^+#Pjc$Z2z z*XS|hcEtv=uF)3lod!TQu)Yya(VjYvyh)S*N&wg8or z9WsqX#Wx~)<|$nl$H=^EYeUHXWS*k z9;Gbr9t}(*9@Ma0nQTS$jvyYtp&(Bp@_)51R3Tl;B{L^2LYdjSMin!8R!HBiLwrqG zI3C~12kj{*rZG0<&NjzNBT|dm8;mdK`VHWQ1FP7)WkC?9~*;cq0pvT)#c^rVy*JyDdK= zy510`m>TEU*H$4yDEnUSg@11Z$C5uRF^|il@X@sD1CQ(bU}H=0PnWRWCshj1O;?tS zbq=7l?ew!@nwn^8(Lchuy>xzS$W(k&rDTI!v~!DY6~6Ov2u{q_Cvo379>?HS&C`Uf z$lTcxIMqD9zLn!~Hr#*WUd0E4-yR$^>+vqz`}8s=@p$MNC=gHk2wnc!r~&LHF`C2M z1-%wm`C0T881?i_1z21eb+d)Qq{%c>a6^MBpE43tecTW!= zRrL@2n#pXK-73oQA&0%M6BF{y?!yQd&NM3*(c_ihWDp=@G5pf^UwqJ*Q8C%vfHUf2 z@7`g`evJfefyZ0vURqyB(jkJ!ZeLqJQqg(v;vQlTSI9#!Hs<*~nsRxJQ*cR9QgF<1 zzOu~7kqntL^LDzCKW0Nsj1GIOgH38y<#^?>^_%hWfluD8iU+;RKvBa#{5^7bHVRH> zv00`7jO&61+P3h|{cZ5a!!~sPP|7=Zd3E9t=Y-WKdh$P#Oz4D)Ve7l{A z0KL6*7v{lxef{NKvb8uXQ4M^#{}8GIkMM+Rt3HQq8c~#8210KAuGjtLeaUt-yHmyW zW?=kyI(QE$D#;wPBa>jcx&XFF{;JDAd-w9ileH+{239%w;$xQIBs(TRB3pRlHso2T zPh79d0d7z5Z;)V>x?Nt1ny8oq3QAz7o3mLa4)cggHURFKpZ-}T>#KvI!_9#QFjf6!Mn42L(YFovXTurLV2)e?68kY z%6KhBI$@R5EEw@n64zLCB;7qr()+sEFi?k42Il-oJW138O2qnj2g-E%$#EeG;`Bm} z%d3ta67{sdk9I?<#XrZhQ=&UD zRy}l5{YZ79v-KSKc)uc)7;BwTys5L<;&Y|6trp1iHvpvCW zbUlw8lHGP^Uqy7r#^kxgGfTYOqwR&}l-Z^j(wqv|4PUgi%x$s2l>)D*xx3hjjh^(Z&d~v-cl1$^ezM(IF zs}*26CA=?}M1W+`-h!P1lXVpp>5s#FqZR3Y^-okjCXfDy^NX;;vt2i-veW+QwR9y; zp>kJ-6kY$jPU4Wilo))pcCp|k@^A&Xz9^h@sW6~{i4Yt9%s8P52W+0_keTtGe%LHd zbnf+65AF-o#W#%laJ;Ocztp_v^)lwp-dkN=_v-6h6N1C9{8-aU%y=!9Ou6*vuwIIU z1Ml*;KX$z?4AU0>)JmkdyWFuy7smSC{-kuHx-5)2x{1n&K9Aj$GB5-*zGaZEyyRF1 z=J`n~J3T%}P@Z#H!QOoJ?YCG{)P%|Cs&hpnBBIJO?Lqi}HBV)swM*`OfywQ2&_o~=U$xA+F)FRD(Tv__G7F}*n! zvFfP&tg`Kg-|~WPGv%UQAt@t;@IhyFW9yqqVopqh!FKI<Oufb*%Rb+HAj&QbINantM~Y75;TawZx})_v*;VvFUJ5dYm z<+T_Zk>zCJlQ1io&Fo;Q9F$?Ntm^}NkDgG{@caT}6t##_yuS=(*%u8apn-7-^Z4;$ zx6eZ%@>RD;PM1{om=rrn<+)Fxq$9kj!syUo;8U`Z#1S)Mt@pQq&Z6NfpN#n&l6ijK z|KKExw3rEafd$+O%+}FR0bfGEJADei`{K#5yRa5{pwb0@wBD^yhmzNjG;Jl7DAg^H zGawhFIj!uCZHZzjU%vaQ5dLfP%{^e8eT0>*MpFGLo`cdaP=(Kql<-U>)%fvNMF>Zn zBTen+yP2tQxvi2LB2<(Bs!9>Kw9I|vxJOA+Kmd3eyH;A{v{a`^eHV%|oU0WY{mFN| zn3_twE;zZApZ+U7bF1Q4|=tG&xQ5q^_WQ#%hix~~wF3mRy#UX$>y$Wtyl zJ2%u=bP!kx{)@c!r`Q4FzcF(fZ^S7>QUlm#GR&AJ0Yu|BCmRiN<f~<5)?P+xE@R>pzmI>fdQUl{a6WPICz6;eJ(vy$iC!aOA0^1wE zNcaJ1#nF_BfJth}vohss?{ZjIMX;i_m-b^X#cnf)f0)$Ilb}iDLubdM(bC((l3B_( zJ!sK~$WGMOA&uTxZBjm&&=BBa&Wu>ZVB?$ZoR|A$*A3_`oaQ`2ywSmfQvF;wtnI6U zNoCo1iaQ*ki<;>zi`BM>uBCayRNR~pkEyz-<$-%H4y7bBiUwk1ouZ$i-w4zyxk~f`z!kduuqfrYZ!q z5tD^K`j@DE6w+iyZCy?kThylxz#UnM7Ul=G9-NDhEIdW+9->7vze2f3;3I|)jJhzM zU4Uh`rf1%LrH8rdJcu7Sm$}e8!s@&xZwLKn%vEkyeOfx`e=W^;HzL()mhy`6($7+hb27&%P&08MiDF^C)CGH~l2>Gx1&K{7j z7NHVgKGqP8VWA1|>$T++@q2xsHv?!CCLj|p9m+DEagMjpm5dwEj|i4{i`qYJWIhzJh@w zC+vWJUm>h=c1ae(>s&_^OJAJ)TMN}wp##X=v`vc&bRde?T>G95`jb9jx@S<;#ckjA z9DT^HP=Kkx9`P60_38@quCu&mwB_CN<37w|6A*(-id~hHW{?0xWac&E5mHI~U~1rr zoN+^bZ*=;3W;nQhWlfi>J){DhTBh7lB}rNrT>w(Q+@htr`seV^$l)!0gTiZ16##mb zp`VMI%W`8^`@#htq`qZ|GPv}AYcP#WG93HNU2d!b&M@9$2@{3)nyRY@KtWxyKk%pf zAw(&%Hw0Tp>&3s6b-5ToGFv{#&YmpuaW$uEF_n$rditdvk?e5{Ko(eZ?I3zp5=$K$ z3uY-f?NWiUO9-n4ER35JQNK-jN?OsgIibE9T-GFw0TyIHaP~$!k=Tqe*xBK4qZMxd zk5{c@v2|#bv6^k+b(nR6$%t`j{%C;o_`I(yOCY|C37%}x=}lG?MkA4iBb8iRH|HZg zez`WDEnMPxCx%+d#wQmpq%8+U)xY0Y9{L)qRyfn+%L$d6%}2q9)@K~A04J71@j$2a zKn}P)y!R{IM&132LkMTwP6qF?{fggXM#=`PJpCOzh6TvRo{rsM>pllUjD;^uto+4$ zBaWS}!s3ouBndaaN8mPx@Arg@_rCvAbB&1RiUqa&SzjEpBn!{wW= z*M!9I8oi1xi`2<(SMe)Hj#^MRuHqAlI5dX|eW2q%O#ID*i`-0F;T;d?*&%Qxc|##N zA|4)~wk|*1o&VBP6gSw(^W2tN`sGH->r2~dLA%KMn8XGT-^xl ziQk_!kqO-WM}h7tfCfEJ9FqMFCqm3=^!EHA``0TLdi=MperF17JsQ+{Oz@y>dbQDc z9>3S55r|0Jj})($m4=~ze~Jlf;so)d$5c?w&sty)@@b>%dqP$EB2|Vv>0)gsIh#xC zdS`Og=DuWGR&y*n&7U0~l02({JpIf}QUg;MQM5gGTT z*9xNbCxP&bZTpindUyB+E&8e-*B*poBEV~hJW59zr5E%qU>)e@pPc}f3eYHfo46CX zd+94ACEc&>vOmOh0Y59BpyofpW{x;BYkv5!Z_|B8qPWQ(jtD?+HVt{N%vpp2{jgV| zJXHNQ6-n3o=yDcJ6|^mHc!_}LO|)wG1`8Jo3MEg%$zW!6czY`oE4YJYL3Y_RHIEp7 zX?k6}qylV5Bv@z!2={>Y3c6LIdNZn=GZ1!tNK6#k`iy34wh|WJ zC};IuL9&q@-Loyn)lsxFbQpVaHY=1NJedoSgye@zW%~%2n9_sKePJ!q0ar`oyKUYd z_Q{mj&g9do%!tHq({D(q_Pt?yW+=`Y?umjZNs9}?9Fnemo@BMCkVjUpY{mCFs^GGF z@8=;aEyS3l6)5qR{3-?OU8i*B4HF#1l8Z7sBDgip*Ave|k^q0-%cR&3!IjUVH9w3* zEiv+NJJxT`nz`z$o>fT7*RbZ!bcElEovmY4M5f+^Ws_qFA%A2Z&o9d@8||gjPtb4S z2V?x&20#G(keDt(2)AtnarKntBc6vNGU+135usUGR|vc=O$r5{#62%^a9si0p`dA@ zZ;GDUh91c9L1d7-ESB$BS>OG>Tvsd+V*R{++6Q#WMckFR$z_EsfFLEel>xe;grY>~ z^^5-U#7sQefW1qz0yhUBB(P*>BcKjaL_gzwb(Z3^8c#&9 zMbv4X4uc-a5zz_g=2#P~-Cr;UyM%m7FR#J+rbbK}*lRqAQP67l_n)tJ{{wrvW)c+e zrfzsXuBm(xjB2Un^V9SePsAM+WC+LDcg2){H|9Z8x76M84N6+vqy|Q+=7{nCHmR}H zdX21r4wUU!fJTzpou8e1-kBA4=ZOa{0)j77eBiqtMrViTS{@ z%m?b~Xx#@=V161Rmyo(Wu{)=^GYu{G*6<$SBMJe@HM%h5@<%wO=|}40yR^gpMb}0Q z2d$m2AC*s%kpc)!ET_vfX=MuOTqY!Cl<+FfM|PSB_8ddO*nIL4i}why(v-tr8)ZpjO7owFTa9ey2&JWLem`=wzy=#ytnr%X& z*dO`*9>SH%SF}F8!i|l7yGbqaJ_?Mov7v$KspUEv35XCzt{2)nl$Vc4 zOAuc0XaoVA3C2bovDthEHIRDZtuDTN2?kMa_z2U@KVBkn{`E=vMK30&Fi_HQV(1hl zO961}u}dxLH<7LI+{R9G!1rZP}7)_*P9MrdqTz%5@j>ts3~CqtiVN|UaTTFuDmi(#j| zMtflEoA37fzTq6+7w1uyslC;VdY+hsJQQ~H4p4eDxwCIG5T)|ZqQDDCFF{um?!R+@ zAiVF*a(MS(AEr|_KdP8L{-MzqXdHbUHad8y0PXGZf1_2hV(>@-oO721$q=az=||l8 zt37O+$DO%RF0duf+pKX4yrzFbwyIAZdw)euR0d0QrIex!wRy z(vA{6A489nNS$=o(PclICDeS#c;%uXY^mJz6WFcAPfVstk~+zIM|oCUS>+P6Og_!# zQKMCo;4u7v(UC()Up7cNn`iDUMi`>A(RRw1L~xcDJ5;Fi7m{u<=Lte-OPY4RXAbE7 zddrCPsA7oRjMlupJSt4O5aM#E-K!fyZPT={;8TTvLwDah^s21TB8@-huwcZW(%&z1 zn?i!aY-xXH7$wsu0f1 znDa!lZY>l&?(wUslMt0C0IWeYCRXJSjQlSJDPcqP&GR&J=9MyJ4d^4>n&J0`bz{Nq zsEB-pzFAc7e%u}!O8o`E-)`o& z1dP5>#`C)lh;Or_cS_7x8G764p@6l7M!$su5@SCaH-omTUg~?mE6>4>9c~OsK+2v! zHtUXiLi^*Oh0F$_UMP$0UH2c-*h(XalR(`X?Rmv5UpKFxYgo*2=1(LAX)F>b_%B(Y zFD-^jj^T~9a4f&XE2KxEB_wA{Aq7mFdi4HSapIr2Y=Di(r&Z4FVjq2b^`fPt{iW_T zXjcnbp=&S34hq}~M+}^__%CyAE6+r2>=$4PpO0Ugf8Wrhc{LAXrFa7%CbPHv1~D1G z6dNN8Gzd?le2{UiXBbay)-#o!4eC})e+RMSQliXY0-uN-m^1N=7+ga2uR3^U#e`df zf_5=w)a(g$1skd0M^el`OU4cRPqd#O5e)M)k*$m);-Jd0J&jnkk~qB_4&cIOZ&K-6 zr}eN7^WjCW`fZJRr$ZA-|^cOg_fn12td&LWbU=Ds45OY9b09K!-c4Wa4Q;^K~2 zxCpEPTYcYs9Y6khyD8t2=mo)m1q_I=H-R_OTkv8I0+!~2ssDB#<2U3*-3nuS(YuD; zbr?Uk%E-qPmWWY)O9W{PSaNV^QbA4dz8Bf@-P*j^4OasD2GIvsLZ&~T$9mV>(L0*- zlwcwFNX?L7b!4hWJFI>J9$0EYcK^7!A8IQXHh1r>2DU|g^P|dfr%3H(d387be%R^L z)DT9}CN{;unw?2EI=zA1g1>HK;^7}xrrn%C-0|%>G4Y&4wPX4yCHn&NZ}x)GSJVNR zr9w92{Of(!`w?<<9NCYvvB)fQ zXqKc3qwtHk03G6RGCN!)5$BVnZOS6}Zp7M3TNxfhgy;VM1WY)Wi?J&VD zsjf@9c~D!`>;Md5Z^&GXd^*D3D5S4!9nX}a^>B}@LE^h8hIAvyQj84}%|#D09h;mY z`#n%O*8uAwD}MqNO**D>_Hiv9Q@-EymBz)40St!tB@^?E01DJ|OMs~G#K6#t8Z-gt za|q|e+|0Q`J*zVufPP0fyVk*vBearz6vaGRtiAEiOxR=8c5LJb^KAV5|1z?B8CqBLVhK@E2G@4 z+hSd}UrU0>_h^zI@h&l<;jFSo36dh>uN(R8`pGdCBj{L)!S;!dREP=Vsj)&@YexO^ zsEUfSoDo!5^5fg5(3dS(-hG-3x^x~z-~Svc;Vtf+$oEf!&o^4DNolOFYXYq{A^=Hz zMSunkYdM#a?p^Sy`v?XKfN_o(&{maQtP~hx`3s6;{pPP3ibC@|`rpZ>ogRm@MqPoi z4MBm8f#OCXDS!}p)E)LyXUW@|OrNisXEq!9>cRQqp&k>t>8 zQ(@BL!c^#lPs?gWtv@$!nRR7!j(m{Brn5-$q&ttY59-zeetTKb(K4v$;3!O>{1){_ z#QJUM0dJ>D`|aGVBpscEzB*m_yVf_53e?>Ic(DQ$x?Q z>okSfDZ$zG5mQ5piZ0izX`S!>b+hfA3JsST)YLnyapuW}N z^D8Dg#V7+j&`a7zdB1&DkA*gS#h!KWdp)P>P=Rj*AaLK(FeG?Is8Dx%e1qpJBH#(1 zFQXyvaxp!h*7F23K5NCE4dG2d+gPGkXMdrCfZ-9$#;;WP{w1h+t=?!+gL;5f^d%3m z3D}aGBIHHzY6Si_3q~H`8v2wBxuyVB#0DRpTsh5x-m%!{yT%FX2b)SjbjKJC_A-Fx zH${7#c(}&?bN7q?o|K?VyBgK~yV>2s0u!I(1sxn;CoRFh#{^CnR*CRZ(mBFW?LL)F zYI7*M?RV%$KTwVo{&FJ2M?1hyC-+YuQDJMEw#vDxJkQ}-D-YR*?+1F*>P6z#RO~oS ze(_@r8tc-!3bgkX_YQYfXB-?wUI%QaHJ{K!5h8;CYOFBhq#M#kvclr@Vv?$ZqQh3- zRAStk?;;_seD^*+#`x7yTzzj6cGeZ15q|Dn#=pdD7HUg7o`xyiF8c_Tl zG*GrS>{hv7R&(!!Ny^`Dc=Q~XumC#|mV18Op5X6#S}^U>H|=dFij8Ao48dja2p+hz z+5D2AUFObX;L}W)BW@jZXL}dvbCZT13?j9MNvcbw@+Eo_6_IW&R~!M%PCm4NH9Tbu z2pbi{6tgLE9w`SB#uyTEkos@LBLZC^5eg|Q&QNZPA_<`G(JLyW=a6*FO{?i0^FrRZ zg28W%Wl`O^yV0lvi$Wt1E)@zIVLp#2fw7 z+hHJlAa7(K{MYe_Uk#0+4=hbQV_@+rK6?1?8FNEkRs-=u>VKxY`$o_dmEzVNqrM?T zh8z!Lz`!bp3lIi ztxwj+U<2!$yg}3Ca<^q?NlBcvjV+z4n^hNm6%Vrh^F}8Ag%(dnl!L7~c_tb(?IjKD zuYN0-kbk}K3&v0p*w>AaaAzY(+aiBote4exx#e%*Tg9k{lAljaqi9^U>PZd z-rv)gK+I~#!ZH%r86PSBLHNZz#d~? zWiZg&!9P>w3$-o_y&U^F*pTUO(`TPW6lp7+8$CvPq3}UAD?0b+MqQu;Bi<NMo$Dnb7Ce9{$)UG=3FIx`&C%| zSoMXFR3L(A3BQLcc!a`!b=b`T`?Xl$8iwK8Sxi)#-5ux z7s3%yO{5@t5_H{ddWO(5QS`~0XWHVWC;r6*jq4d_)D8|6`Dr7d86bysRZOfqUx+(fGryQG~Xj-T1qCaD?7G2vdu_4wC|a%R{Yf2tEH z<&BAuarr`=%-I{_C4Y0E}((sy%v*q&8@3NNR&=FmA9^Gvv1=3RRE zg11&&HrVo;*0Zzx#3ECjs0`db59>ll)QR1n{2=r=nTT6HxRc7=Byi`8-;3pL{+ z*0f?lbiEavSI=Ggqt;CML=Ogku1o~K17-+Bvs5B!a%FQme`H{ z+#3~z?GvR_a+a5&Afz*<=mAjl>0-|f#-0)w9_$SX!-G(OM8ja7x6J7fDd!;q&Le|{ zucPk4T>jiadn~cP>0v0al65(?BTK&#Ua(%>K8xIr~bWkC3U>L1fbrc8MD=Y9fGxu}g$*6X>cbS0jDkF=B zn?ObebaRuSn>7l&=b9wczH4o`vi!#90Wzv=i{@*3&YY(sMe<$RP^Fkk>*Zr-=Nd{2 zQUW*5GIR8YkYI1`VivhAZ7MY!O5iCy$W)f+A&TF_gX`}c9_q~5bap<1%{h=T@3Sm# z^DZ9d$v)!O*`kX>os=JVG{Pr}RLF%==M|oPfo0UPkAB36;}xbYE$edJ+{DS8r7hse zrA|kXTU((`SiC=!M@-i@h~CP9`Hf*A#x$5$dD8+l9Kc32o)bd}R;CGlFkzstoi&_X zHW^H6E~`gd(F&aFNIm_gZXQKxCl74x{$~-!`W&B62_G$~{^2osk<%J(_Bg@)_zqn> z?6Q~=BXjhc@>=(QFupp))o*yU2c5B^;U7DOr8_S)bH76&^Qmvk+chZ*^Zxmt&5xce z%#Zr@D$a@Or!NNcb5r}0#c!XrV4{)Ct}gaoqa*4EMQdlSG6;CSdfHqbGACg^|BqH~ zoE$wqh+osHCEP=b^exXKcUNgt$Xm`hUBATUFSWmwwB`-G=cAy{cdl#AZ?m9>Q(QL zNmU$fVGBP8^unnbbF;PIq<~c>viUWDut!=6A#Un3)d7PAYAy;U0s=o11efx%Jtfom zlXU5~zm$tI%v6^3c``cSG-BxVf6TVeSRU_Y`8)v>E(7|$Rtz9 z4^Z_*-|9Vwb5zOHc0ZXs(aRH{hy4f&;|h8-6A(7`Z^D!WP&=D1`4SK?;8}c#XzQfd z!ez{J6y@t&o3an#@@<_vUK|LP%mq_h<8ihnkHc?Q8<`d&Al|xPC;u4zQBq@=kgy99 zuEEmO9eEy(n55l(NMO8yr$*hC#0m#wx0QZ(m` zBLu-S_6C&3QIsyN;S?PE3;mxGH#A`5=Q$Altm=F;*4$mVZ{^2ZYe}4vl0qVvL&zVl zeNEw}n^QeDO0erZYgjDqK0-MH5|?&;2cRAvVZ1l51d*#tzOQ%baQCIW0w6^)N@VK* z#$0BR@0ERU==(FzBEnI^c2i;f$hWPSNHnr##X2On3Ms^n%WCAs|C>Z#n}!D@Z--u4j&L%H7>d1B zTl|)Pq=Vu=-Fe>qLmAN3M)W47wTD49L>^1J5Iu+4()N?2wd5=ZK8W5imz4amNkZv< zZ<2krdnZy?KVSn}hX#QcEkxDdwD0y9ILt7ni&TU_VN)I6gEWXF3#Q|eo%rl^Nv-*& zbLb+PFS9|T$5D0hZ4Xuxl7{b$8GvC5Jvxjky?WZ#?&?&SmSQCUZs_2q@&djL~Kh8W{g0wXveTRDRyeki|TJT*^c6G4uaZs-CpaQ zNO*u{!bazRnzr%oR9HweU=y9`M27OZsx@r8l>?7i_G$^T0G#9mID2 zHyPmHK5zkMl6=hPH)54SEGskfWLUctm$)%TPtRAsV zuNg@cVuh-~MZ#EkW{y(Y*`}dEy3ZiW4HF$bmzg= zvAN6G#ooWu2hnYaiNBY-w9ZMAH4&v!YB4NZMoNZg!=9Z#*tTBj3D|50x?@VvAbd`FbUvpE zegw>26M<8Li@y(iLA%%wJ%NimohR`U&|=s`Js*jC-ZN@^wJS*1@}J{O!LweEwB!-Q z5T%mzF2l+)E`sL-uWsJzH%RX!Bv7&wCvCghEQ~_#BHOm5OSSZ8<$}fArF9snP=Cj! zq8m&8rc5-uBM;jo9g>p%LsG|3+5(YZaVq+-j}XOln+a}dJ65>Kj-KOUNb&ts-M1!u zN7P@jYYSCVSkJ}$AIfI~#eLvglJqSC@h3@qiEP@=UuThMAoV$8;PGpyML9ng;Qjs) z#4I9bmD}SM^u>>i4hO8F7v?U2m!lsEFfW;fJkh^H#@XWmi$no{kYZ@2TY$Y|gCn|-3GcVzw5g@}c zU}+Nn3R`*D?pWWw`XqX2Lt6V}WU2@b`{Mo{OwFx}RCU*m2skzMR=M;8J3Y!xK0R02 z>JQu_u%|4P&DcJy0;Hzrr0fz2k=ZHA0NKnL9`%_0)`j1@gA=?b) zYjy=?_q@JqA34B5hO}DJb5R+!MEA+1{taT;CJZs0?R{Y?b5u)SO$34hld zs!Ussii5||E3qcs?{L~w&g+aYX#GteFsCzUmH)<|HB3?_%mSu0vsmw&!j`+><5aN# z8wT(7bS5`)>onk@>onCv7$>)^3<#Nn)CS*xiDH;&Du0V5q76tnKrXiYJH~q~5i|HV z|Fzm!<-R%mObrl7+*q=qQRk*M_+b-4KAy$&Q4v8w0*36SMsSl>NR_p$`Mm#tLI~+e z(U{oynzQZTx^JE;2mRt~P7+O>DX-QQLD6<>X!-4~X(QZN=*t$B0F4QSAtOe$vHy6u zh(^|i{B+uenKkJ^&;J{8aC;ESvvByxvhGs|ja}5}!}{3PZ4??z9y5s+6k$({k0yh@ zbAmY=IU73To;G?WJS~~bv0=-%1@tSBBrOV(`8;8N5L(?J>@OtNaz)pm37Fkd_dGx* z9f!WRK0-z2pnBO3Fa_mQtwP1W0-O)NH=eIanUzapi8i9XtEh_E*g)5KAvw@|=?6*C zCN{0eWf_VivMk|R@EdI7k82;h8%aJAZDOsWc3b8OTR zGk7tE1t@4ktaIX1kbn?KN4AM}?%v;~h)jZv+MJ0#d-`80UHQHg5*O&o*N76#?7$(X z`3F$DNUhKiq@nUbJwbQi=?1&9%iRJK2^}poC^Y;o0a#CGm~)=$fyy2ftlEL&EpHlB=+lj;x|&=qvLgayqFDgsf&?txo|B9yqn=z^ZE6S zC%HGCM%)&q6@dl8W@f1Mrvg$ecXNB{ri^mG#qnyC8h(eJ;e&HR3B@H!Xm<#i>E8VQ zU*AvvMd~&n@?r>$;e>1c&->DICyWID5-9`D(@kj?F;tzlKqvr1vw%XI=mN%jSrwIx zOaezj@??^5!SK%t!Mh10h;in0%idEcZ1HwNIy;!1iUcC>xU+t?r;&kqs_OUfM*SmQPelW z1Sb#t0Ab%8^o!2Ga4MrP>qm$LKIfrasqrpby&WwNu4p^vssJyx4gu|qn|)C$F)jweaqVyEa7ZC8ul@er`o&$G1{XnTA5qk$zs^a^TGV{rpWE<9d1`RCK z_=|0DIPI`ZxzRQ{}oHMTf9tf&w@`sU)Jqc@$9O7cCS9S z{z+?Mp>~-^U1yKGYJKdZ!+Dn;ZCmKO8qm3oiBAmeua5}CpTpsP1+$C$WK*rq;V^uc z*-&=s|JI|eAuJg3?Lz2}O?tRATb5VzB~5z+*l8*-i4i&4^2(k04zOU$T)#BD*aU3d zw@lQ|g8cdx9QUbXll_}xVU`znN8qL@4@Y8o7hK`yX}h*hRvK>}Tw%a&xk+Kx#K$(_ z(Xx{Vxb~IH9}Is&0gemuH1FU2CeSyUd$F5iK zmQPl*0Aq$e&z@~cZsRTG$@e!=l8+;R?n^V~{xb+>mNm~G()3ROKXS8fe($$t-Y0_^ zt%3@6XE0`@0CIsqv+F_3q;d4MvtF%QRhiZ9n!?HLv5ZcO8w!+Pz7HA+bsu|U)0o)> zt-y)DijzW}%2YdCYOy(bwD0ggT_oW2u`ux^*6*VKv{wP~UW4cL-}8o1g_+FkH<`bv zwRbib?tgLjJ}>`v=gm(3GZt*Pyi^zrW)W45hk0ME>(qV>KEfQ+e4lis(JoIkWGQlB zXV+vSd5tx5*tDudfIbEMKVKoD)gM4M-5hE;GTE4681O!>F>Qy)Y%Xwwb$lw8)<5lE zRu66)`Ya(9aTcrBaqfI|Yv-~pAo(sV6~Em+JW9{%OE{h!R*xU_qgRM?rm3QXW{5q!_~%& z0U-XbU+qqLW&3lMggFppA~HH=xY{;}58 z=*bzzu8sMF^-qJ>t@+=QrIR#dS$lC~xs`}CjLBy z8;ym>DBs6rr_O#0`Yj5rpp+>r-@@uM`A{fvI}9uAWeWO(TViswL-dU{9VbTeVc~g! z3HEeEgw%(OAJ0`SJu;X3F<5gy73&zs`_prTSEz4Xzj@PoWmZWiIK=A+86;tbA<2c} zyOZ3FB*#a;LN?3k0Ur4p>fhX;{^Rc8;(HrFKi<@ErZD@e*=C0)JuB)?#@gY<8zk9J z{-+=(cmV1{!1=1aaE;jF-&~W>337ahWfN+m3svu(g0d&+ER}^Rq2zqH0EJcHub!UX z;$Jg%`XA^1P*D|C#(eCf(%7t?i40(HAI58HbvWi=TFesOxp_^L%(V#HlKJ z*SGz|nEn|IRNd9qMRy`3G&I38#rC)dq4Ig)&9*Z57#43g#$7+Fo&5F*L@x7gjz&}g zPMMC`v9+U~mJ6O%VjqA46GVt>@rJhCbr6#if_^(o-ojA72|+-@Af}>v?D{GFr6qv0 zNuC5X)UIa#CL`$y!I1{&($J(N%R!oQJKgPjJ?n>hBKmS9KtMZ-)WA?rTz&}W-*yJo z=wAgeav52KM%YeA!g12+f@1P8eLQzzL2@}Gt5n00qumeqj$H3S5lR;0J-5Rvv9Vj=qDktt-oD`a#U{6evJT0JTyD!fd2kkui%l_giDMW0 z$hof4!nqEQa#=g+RPYe1^B}m&3kp@PVNo4#SnXfRkK(h=#YH|SOX!o-(Xau#9O-`xnTG?P{IxM zs14sXw1z_>XyZp(65$4eZ&ARWkHB+pM=K#kCa{9D7cX8U-YVX+ZU|hSI$t|p%4E69 zEWkZe4>HpN$vuB(TzZWD!+O&gS_NG&0yM5{BQNwc5wEaqH99eJZGnb4cI-?#2tC6hd}l6;@M1K)l5qb7^c+yS&UJ8>G6N--hk#pM9`;}#od*2t(b*;rrBQ#+s2+d6ye*5BHZ9{$X(@D!*;LYJ0KTUXtEsOjkn!bhcfJo zJejR&q8exC)cok%bgYrP3-d;YS5`=Fi76lpB?jLTN)yt@cIq-;x3uCYt%ZXk#t$3S z&0;Z<#z)P7Z)o>QoE)-81;AP_X5v?)H6OqdBu9Hrq^Pc9ts9}HiVIR+3&~@ z+5WlPRK_lPlC}`9ahwc>(Y1EucBPi0??dvr-bmmw1p(?2`E{_MK;PE4^92F_kOMNN zZ_UrI-VzL#G0j*s1I@(va*TG&Ci91fwqZ2boe)PQ<%MFZ@63EPk9+bf_LiLiGQ!Z) z_+gi!em2rHkd4C&gX|gwpNZg2)P(I4+}lPJlRF829Vj zCyQ##CStRto-OKy61-Kr_c2Z9oTUxcMI2(xuzM>25;B{#S?gK)U~I^= zydu2CQRlFtNs|Bd(+rgNTsR(C?Fa6R=*tF*e9WRP>Hfi^@6kKT&>EDkz1?LuyGt%y z2Fd;1*Eat{a;2w=s4zICMPsxQv%tctB+cNp;@E@xz^oX#;gO*MMV*fr3m0N6(6}PB z%m0hIHxH+J?c2s#dnrSSB%y&2Q8Yw8_-XFSjIW5$0YQ)JPO2PZIb z@2h{p14uO2b-xhE`H@FNv{IiLLVAZ(RpWAcO3%7Yx(;=k5!QlwgM#eg4BF)fI%gcB z4PDi!2!u9U%}$QSFrQa}Ph6n$VhRRtUCI6NrM@0kyVYhNaSO1H5C7)i%Qyf9SiB6v z#;pY?;3mkvvZ91_m2CVEyQ|M%*FNLzlg~c866Vggcd;5nMeqSA()4jrelI|6_MOY>5Km=cWoi$ z?MYbIeW!3mPJi#GO|mafkqD_|>Th)Pk!}ouF%r+SuUr{jHfTyJ7K5*HmQ=7YKsu{#KA_9H4+D|5=~rC%%Pft&pMzJIewqWT zzIkLVxg$pG7_zEB_E2M6oz3I9z`pJZzz?n!FR87a3UT-?*XNTldF1-@b<`uc5t145 z1M*%(p$Q6lLcu;ShtBihc5_m)`y)N1#TiR$CgiKgLA5qb9adrO$;n9%hfZMBS&#Nd z-Ah_MX}%a$v&s1BN-MiQ^e*xkkx-qz{v9pI>YpId5V687O$3_y0k)tf8B%Ba?8UxK zIQ;srN69XNcUOIqxO9Km|Nrp|pZSP`7V7c5Cs7X{z47sd{Nu{sE)#HJjfFgBtPqLa zsUUz&#fmif=!N&c2^d}X1l$Itd!;qn97cWm0X}E zlJwxB=ga2Id+k*@ee&^ucadN)<(U$u3dG0>O}8l!jmsg4&(%#~%5m;1V66$@?hBMR zoaD`U+1;?0EwKqKp>I3#*R3QYr-a3vf zaqtoF4v1TfjEp31miwm7v^IZzV=uT#e)|HP71~PF_V_4zmDK^M`gO(+3bk3Kg9Bj( za?TXy9FUglw)^Jy2Hn^#y#mFk*YCvOy_-n)zO9T*Io|LnJw;5w`ww?v@WaV{$(x8I z$)IBqh%F~&=E1g;F_6{MUPCLm9y(u1kDLxZw%SD%lGS|i;V1FsP|rvXek_pv*)vet z)%kuqQ7B!`_nG2QoOzq6p+Y~*`J*qmIO7omPycl;iWrBhHZe2>@w)RV@e2coP&BL* zbv9@r2tbG<;h19m>4iIha8y@S3T2^F7>Qz9#0$*J)2&DF?|rKZN@UC%$$at6NB}%n zK97X_N)!aZrwC#T&F$*8*N_=GO9W#>idLhLoZHWFyYtGsnk9s7Mg$EJ3_AgaE5<5k z+7xA=#Z-K!8*}~Wt1(v^JJ9+tCiS=~JMKYKzCiZ1lIlY)@#TY$M$@3ePE8|9fb_q{ zz7yXGaoZ9pQmB1=0qGuuwQhB71!_8)SKA=AB!c&a%%D%|*~ zW^0km>Ln+c zUBipXpL-Q*I(!*&)04DeukvDJFvE&*c6qTwY)sFjxrr>m{DRrlcFe2KPX%1d5rVh% zsD7S;NlE4W2(Z@|`h>$GcsxEdWxe6blt|Xoa8DkwZ57LA_WzGnLcinHxJfFUWR)wS z-N}7|Uqiss(U}c{hR?SBX}xikMZJq{Z^=3D~?soR31A}YBVM) zDEbGu2WxgVA~=rRC&eL!{_9XpT1$eAyg5JBnxOIBukCslp^Rr%nKLvH-UjYG^Sz$7cN<%RZA?|OuOinB^$L+&cPy9ZZqR@KMdNT4rR za}S_Z{%K{jhp5m!HBh;Rkdc0f9n*-Vf$*)4e?+zm8r84ixar5(3FV#C3LJWj(`|N- z6I^gj)am_sWd0mjZ)7$=KGaa!(xA<|6SA#wVJ1Mv zeSv1D5ftKK-|RYA?M6#c&1F%@T=Z`AIB?&+&4uA-&v$Hd;=fdbw6?W z^6pYa`gF3=HHHJ|;-SBe^bso^YN4c)mBv4obQ)YKA6B|y!oYtF+UgOjgPRc|sG@ED zZjf}$2l%rlq8d;ZqyW5RqGM$W5&k41k4Dpj)&AmJK3;b;K4IuF2G^$Pj%cUVDxRQi z?et9{Nig5`-IK*nWpb=6Ek9aLX(I>fWG-Mh1TA90*Moe_qO&tvTvQ69gsGVxAt#FO zG945jKGs+6wpvoK;xMz9NmL(YBQ#hT`wYoH!3~VLrbE_xbM4fVW!1#^d{*aTC{apy*VgDRl zcQFv$d8FbqztxPPCxrF>-8ya$bCy%CO-xC(XI=AQgnpzvu6LP0oNfXl*SYos0O*dj z?)|+LJDoE&JD*i*xpLFXc>TtkBaZj*oBH)d$r5av@ZJO>_h&bY6l@6syxJlOQ|!qc z0Tz;#T&9poJmlilO?7TRyi0ZLRa?}tVIkGIte%hDG)z+dlp`UN_J*-%v#@99y%ww; zWZ|q;fsvV{>B(%BxJinxtjLLM6rgEh6QeoDV<5CK$_dlWiKr$~hg)t*#IZ zFUeh%;CW@r$j|+?paBnq`d}upm!#Q=5IB@EJ%&)Nst@bDc%7M4nmz*y-lY}aA_8mPzwoLGT1Qt zTZ9}Dv|8wURUCfaAeZa;9%fZ*Hm;((`ZLSC$#PA1Vz>-|ncS`HcHlN$y|r$%4TWRg^yxp=7^FLlvum ztC9gRT9-rTm8`T;1CZ3XGzAl^%=n3as|P&kFf`LrwfzC=UO+ataxtl$ld_}(qQeo@ z)Bo#I3&L?11?QYRkDubJgnkcDdW7zFV4#VnUmm#VSE8e5lgSt256W*l5Eq%9draaB)aru@HiO(_H|I6mfm_U& z(Lxbq3W7HdoFq&ihQ%|)I5~bsgVo=5Ft@^)P?S%EcNyeLzKHr*Y z_BxTqnu|)$ZL;J2^&Ee4D?S)&=zdF+O_7}1IFF7Ou`s!El**V)opqtqYEDPi8hmF& zNrS$Clv9sM3yYC8UX!p!(QeJ|t6$Xmboz|?&O!mndxOfW!PV3CNG$qWI@>Al$4J{7 z486TfuP8h;N*M9B{_vDh$*__H7e-z_Z=!P5!_6RCmkA^qM_9#<6LR{|aOw+8w^$`u~ zc;9eSVAqY+$;R|@q{mO0-y{}B(6f#to&6sF# z$H8Key!`6*>(?jmZ%hv&Dj*;yl~-I=9tGlAKKXOD+Sy0dQ#GT!i)0kci7ecMO&F2( z14d1N1X)M9m81wKj)-Y}fMKbdxD9i_4`YbhX;e#+Q&LNvQk#EQAZL1gB*b0TY)V30 zkzh(?(}Bv>Kvvx5Pc#nVr+$?&y_)*7QsdpoJrgN$Mf`NfDnf$s0-M*qhKl?Nw2{mC zn^A%~g^vEU_+Ls$CDZ6A4&?ppYLD4ij8+-x5FX-wc8odD`(}W{w|thlEe#kicSHgt zFT3g7(Za2}r|6O(X-ktM-In46{pGi4E}kU`orN-%`1R4Of1ZWPxXQpHs~k@%W~rB? ziNs)yv3hF844k0zZVjG-1P||*U$-p{VnS8YI+2sLW&4xH1Wd(3dWb{yY&6OA1g;T8 zk95!eAA>b4_FcN+N!Ja)3L+iEEgLuEPmg%Rnk=lJ%*Ew)0BGm_t#r*!%!sTj`+*wr zbkal#P9a5ybE8eB~F8EOJ$Q*KwUUTgI2Dy|KlX}i!|L_HLFpFf+?v9H>Mzt zwuK~hpVf`JX^7QtBKp(m;UPlVBVW&uwQEoztBg61RenNi3K#CD!$hKibqV3k=JbQ@ zNS5to$AOfp{Yej z`I`(MxEc`=A+5P(&0@>iDMT1teKqhCj0b{CKnwNM$zhK7pdCBE8YSC--fls#M~9*n z|2aAOS5AY3!fh^M#X00;o2ZHnt%7kZ>G9-bSFaraag-R*$wm5-UhEow=c!A@$H9tr zXN$quA)__&I1}$Olf%(O%t_uhr?l&u`nerojmJ$i>8Y-I`>L<)Rm!=p-cRl`pwnkf z0_u)V(gQ#(b?LjO!BuKsFlXcpSdMd1xegmqmzLoBOfz`INq`gNy0HRhBIFUzs2PuV z_;4QV`s6F8$6)9dD_d+a+ngJsyi!pf9XEg(iPd6{HM+C-;>C+s@EVm~7WRh`2m?`+ zoc+*>nU9A#5y${H@4d>%0J|Ad^Ea^xI!du0?tXzFPvpdHziBBC9t|G_a-kCpJ}M}! zAd|S!0E%8W4qE4-xW3JBV8TIRKN}>!6!-ruj>}0n;Nyn$!Lo`BN;?Jkldb}-xa_P~ zXoOjTCrz1pVA;j9N{l(J;u!qJ-9vXE7!+l8xtYt#N4CL$G2OJPd3gA{7OG|8` zv~dv$KcdRe`}y?3q7&)1O-#B&cx+ETxT!%f9?!n;T*L((gq5uI=rz3688YYH|M?M; zDGsi2mBdd*#`pv(nny?aYqrJ9C~OlG zjrpSjk7;eCSH$SAfswuP)$kT6*^D%?I@rxqTesAc`a4~XohWh>{7KdARz_L^jr5BT zCk-p}janq$I=R{xaJ3vea0|M23=qfKr}s{Wv!%9AmaDP2E>U;R)sMB}Hl z^zU^=m*yPt3Vl*sN-dN!tuI}mR~N{LQE0BjG*@l2VtPWD=7YpEYs~cqC3gphLD(tM z1kv1uP4ta1KQQWtf9(Q2g+j)37~&wyN2G(i`4*~!3HvwP9l8F&-P*O;^yx+UircsW zNr9o4kf`{0AoLUvBisbri8zxhyv}JTMXUNlTz|z!Opt6-&H=TR=uE7Y4cbBFTRw

H0E}95{x#;_6#}DmusnwT>yv8o4`2*j4(_T^<;JKRGGuS#4vx_ufDIOTOc_GiXitSYALOIt9=|X2W}%Pa zA|{BHGr6=2+4AHWm^r#sCuq@rEux{<(N6T9h$uqC$=Pe>m1>Wf9144JVA19|neOyb6aM|L)Q?6y={B1eUMwW8U-DN$k2-g9PgsAFs+74dB5 z$%yLapCmW01wABQ09c) zsFOjjMgJ=}SW6v!5?w$#C_&6Y~z~!3Q9A8di(iKAxB--;5hIA(|qhz!c74k=F-!= zKM*+yPO{GS9Yn&6e6K*Fzz|5C6Ne$V3`6kuYK2`CMFgacx2?cF@8c`cuH&tviW!XUn_3gW9D(mEE)c5Lo{GOhg^VRnO?NUItCdM{KPC zlqdX^B+EW&1AzQ@In91icAlY4w{yNz-~VPWh5pgDql!!KDGfYoXVI> zlQpVGef9`TB*eZ*K_MTSqLndg$!MyCvu(P^hgzN^4_yfj?^mA3q>5@IP2R8Ptx_kO z`{lq(NAtCM(B4qNb&9_>W&pspKoX!rTM1ug;d1ylJ|yY`Js(Xa)>hm1>nwArHZn*h zE#8a&HW7RHHAi{nLf;Mys~P>zuNsi59+@$@TuE)@D2<_qb();GLW|*Vy?K#p*&GrM zkyO~Q9ClslCO10}+g|o>)|t@;-li>ioB9(uMh8WTym*_Eo_-id9KnaT8Q(BwgGegg z=ATI;gYTDqksX$5Aq4u$uNpjk`gFN+D} zfRhq~lUg(oPz=91a?(NxbIZ1)LBoUQi}-4FeEdyb^z1us41iRIUfifdLbIY0d_GK6 zb0xHxxV>NXemqoX7Q-Y*HSoLiE9}6fOZhM2rgZR;cixKSgkHK@&z{p=kwBCl*WWtD zy)(FZ{PwEZbVI<*i|o6^2a*9BQ5=tL_lmBkVNDulav3GPx(6d)z3YLTeQPk z7UdwuwSO_|)?erU^psSBQy(C|VE4b?YjV5V4p37bGHbu}cD&mOPZz@vMLXQ8TXR=g zvER|)XxNH-INZ{f671MMCZN4#y~yJ!y?CYhH03K%Js?=W&N5`4#T?AnNj zu9TiR5UFY|!$arAMDva|9vv_qsmevpaEP=`xNyt)p80lv|+ApUh(C$OIfryVHo-|0t z>+fG@aKQatB2mP-mFnkMFrfxfgucyRv%o%U)%_#OW-uPyVx>)%fzF6$?RQM$k-CH%I>@}iYkmhf|E@pq>uhEM zj4^=oftZ=AtkI+Kfiz3DSp9*Ngz2yiohRBxHi#+e);%XEZ6bhYfb!eMdM-5&nj0JU zil3jS%-^vJv$<|$eCdL=@YkhNn2SuryE@N?c`?4Hc$((LI7vVLa}Q4?%-KMBb3M#6 zNSyVBU`u~6adO^HsAQ_nGwf8qB=|Pks4#_cI+|R)zQdxXrg>TV_7+q#%DwDq;npO` z)7ScI01OqxhafNAZ|!JqPk|SLJjL1P7a_v}fEX@}04m1PQ5hpV30ppW{^`-lrBo)!5}YPG{u0-;B_XOL%;SG1 zCUKS^z|NL2O<+2-RD2-;6Sz#6re{+3`7j){^{AVMY@Hur34@QR#c+{Z|5ITJK<5PJ z%_W;A0-c1itM!WzZPO01shys}3+0wGNTv^U@(}zEv%^Z~#RFTelh{lNB5~ALt>^!# z15r15B;VvvPvE-ykV=vS$4QVhg$VEHe}-~GkL3b;vX$R&2K@u09J29%U1;sR4rDeh z51@Uq1i6BzO!UCm8u}BGGaIUlkLGBA^xOOD(xi14kuz%`SIF=ao=2}GxxzLjuYbrD z=$0HqB!RdTk|+>iTj)(xRo3g>FRXwFP}(*-@Rw{MQmI0Mk89oH^s9L2ENb64!;oFZu7s}S` zXFhQVwhE5oMt{|mP@Gs0K*}6d_tlynjn_Vl}&mTtd442hKNu z!U%X7Byrk%r27#v>8ym7+##JBaqhPXQiD-ivyLgW{^tW-tWiVyt*}M;rv+8d!J03h z=m@c#8^WP0Fu^xuy22n`=j;yi0V+<}WP@9KHYT3}!)XYMq&4S{_oRgo z4>q;>KeiT4Q=q*MR&LpAFKq|e=?8%2%|;TSm4U*@L$`p1)GvYy^zf6rc2G`~|l4jG7&u`@4ZyKo9lx!PWj~BY~we|^? zyAwK6A1v194~KB5diAZ9kWRTgG0K;$Qg!qy2e}1pvgKqx+I&C>?s-=b{x8ll{UOu| z_MN_CJk;16Oi}1RL8c4t7ZO4alZWJ7;8wia>hKdtCuiza1pa1u2a=8_!|aLm)>w?5 zf};DYo|pQ2X6{74i;Yf3%~w2SuzDP>67k&0a>kcd7O!N~d+8s!o(CrN=nV`pAc4(o zerx6ZdO#Iv!%=?IiM4QH_CH#~{hH^5`9=(|X#Rc>Udh5}^$4#P6&V)~u4gz>awD0g zRogCPmNyaT=EWzJ@=Ez3|5;nHX!4%}U8y^{7)J6I&Bc8ewioz_k}2ag4VfR#J@~ ze7?|N>QZ8WXm}86J@VcAtUz(1U4NkkWB ziF=-2G`kJsB&5baN{3+AdVo3(RH!ZnN=bW|01;dZmA{D?qf@l!ZPH8<`=OR`@Wt*6 z$xGvhC=mC&UoZ~6$P6%!o~#MkH6+MUa04VPMk_XHAZ0~j{Xex7@nOPPHDTs}nh!M(G1kjB{faK1CxgtJU7%%)_WA$u177eVX{Ymw2#M>4{WMK3$vi-GPB z!c~s?{cS%7{AL2l`1+Q~(*INKX5iIj5cY|{o)Ej4)@XU=11P?A8>3l>IQ2P-sTp=P znNGF@Umw>-Mi~(C!8^+|{-QYXBq}$NnmR2U0Gb68*cfZtEolq+{a*%SwLQF^Z^- zeAXeC56dg;7(6qyxL;hb<%3+XiU`EZRVJ|79IR@O(T349GoA2tu>bfh*B&4pP7z0z>H0L<-U<@lHJ^rQnhdR zt$4{ItLbF67%*a`kz@Nsk4-A|BuxT3;N*MfDGvW3Gtoq#Nl1T|%)Foc<^le0E~BDq z$mHMtt7o^mra80hFlhEf>{FxdfSsio{}#mb4J|^#q{Ej$Xil>)0P^%pIBnn_(6*of zKi9m`8`=n=>>D7w0))$pcRq!mf`fxyCa&9tg_au*1D(BG-bY+W>FK#BDI&tS+K$J8 z-HK!$H8G6c8j+Oz7BY`CKVYlX_Q!GE(oo`U-gmVfW!r~n(hC#V-|;my&9&vyNyy3z zD&!Y~rHCGEOsJZ+ETGgEVqU6&E;Vdp=t&?NZmTR5#$cD_yad+1<9%6#1$WSZ&_cH4 z<=Y_g(VnuAI6iRR_LaeDFg=bx-wNbJIk{5?Vvr(W=I5;#-YW7%cqrMMc#Es;4acHM z4^MJvlHxxttA!MB0D3I*qOHMk<4y+{oQ!k$@OdAgTw7O9Ml_Y4@lY+?0EcZy#j|~d zip>rjo|x*DgT9DC;` z5n;{)TP$>4&OrYh2z;zX?tg48xk~{vAoSX_ACXTLzhVqVnFzNfS}y;bLv|pS@Bfhhm&t z#E8g#?ItUwP5@8=MmVUcF)*+yh98HPYx3`bP5{QT~t{MBkx@IraOS>@c@-2jlYCp0)Am=n`_v0Nz*Mu+P zj&KMW`FjFgG$mw?&-TwHGdT!u5jA9$r|gRibGvI9N=|f3WZ4-U5rXLB0X=ziweLV5 zc<4%$8^Ui~Jjc1#gLaotC?=cWgw~ukBcz^}JLRT$q6_G=oeq=w0Oz=7)_pnG_v?v_ z#<;z*^2ByZLH%vq>X@i6BkhMs4P)gq{@u#KJtMWyvTvK1s#`P4?6ZzKDm-$VUVeQ zHK&^gNf@a#F?pbHzs~tZ!%!syc-~^-UtxO*T4uOL&`1jr9j%dOBg<$T{VUHIo(1UV zQ;du(B~;Z`Ur4U%llk%9*!<+a_!#`F+)jPtLbskKqH_dQ*708y+}jmuc0)Tp^2Y`D zt_ucf*4tZ&EdHx}Oj(I@An}i@B_sZ6Mtl9nanp-@K#B+?F4P$pJ@WK=(9DRUf|?A( zBG`C;G_P49L{iC`)9(P|Q@P~a63r$Uu^so~5FKc*dCK2!o=p~fIQ#mgi7UEG&wf>2 z)odUzPG8si;6HQz|<02ZoH( z@TQa#v|q-&)@yw9A^_wM>~h0A@4S|Zm~CP|tK56_$?&j>oI?P`z$Hz`_{r#$kU<3J zv#n&qz5q3lSp5!(^jUHu%+`c&M{04bv^9{0J{=a)nLSbwh#y!;)jS`jVP@>tX2D9B z-KHSr<@v?-xaveG=#XZREsz?2l!czjwXCz&_F#eN@!NtA!4aQ=f^u=s=LSXxS8*b4 z$C^Ap>EY-BUvP?$+p+rJ-Huy0|Hqeu0zDswzmlEYjt;iGI)|D#xJH7i!6DeU+;S*N zGd~FQem->JwJjE6&JB2l0_A6*M;Kc?{%X~H1Td2ljXQWY*ir<6^>#RRQj~u%EFSQX zYd6A4l<#9hXdps4I%fPy@bFUwH#V+V`?9=T$7iz;rs6sYkP4qpsGxIa@gO(EX-4Qz z5@Lv~f$T!VC}jgcb_t~8#J|WBL)h)m#b_)cQaQ0(Zf(742!b-<#yMn^l3H*|&GVl` z{J8#8-^Wi%ae0R@aAOH}XIsrr48=JEoxK|~K&r+SxEHUDDY?+oZ$UA^QwOBQJS(Ua^ zcK!gbF6sjOV~1ZQvkI0DS^VMYO#Olp!R5$vGVn0;U-?u?2)5YjeINt9h-B7CA}$J9}4zaO=K|aIkxW*o^(&x2ud@@*sgic$JpDb%+n$ z(Z_r1qsqO%7l_;4HN9C`=gB}pB7?q-XryjlN*NDmio$8*d@B1l&0OaS)p<`jcC97b zEPZTv6duF$h{%&|zRC}Sa0Cl6@mVP}27|*564ng>IBDF(oU8a=8>zWMr*NdnUd#;q zSBLng`-eIXyGgsV`};r5U@w@+Z}*%cz(HB3Id;+${z=r;-MxAmA7)o*HnfKnuR+k#L-8+bkk~gs^{z7K`*@`lF5hEy(9LzX1d6gO zsiZM)2n!ImE!8J^ih|`O=nt$hKNlD%^~$YhZnCP#9*SYuPPO7(b`hS-ELAb=clVv~ z@TUG?J0Q$|G+M-ig~I;p{|sj3_Lw~0_kId1)o^C6tJ{T1e!E3pv$DqPPnTM-eiaKV zb*f0N>zJ{Vyk^g2`c2u1ud}1R?3ie0SzGcIS0#XNA=4lJ^${b!;^Aj?>l5bRGloNy zN4>46;9u4e?7|0Mj(yCiSW!W%0z<0*Mwn<06kRKZI!z%P04_S0i>(d91xy zI=hUt)&|f$#!}Oaox%&|@Ur6D9xR=LQ#HjSPcZ#v+FFmED-Tbpm!uz5ee7a-b(_W$ zmJzRQ#F|FD6`Bt9N0*gOprH8QG;x)t8^O@dUkio6qu-}Yu~RlHmm=_jjuZ?*@HB)| zP$>?PHxD2~=(=qYEuVuV&E}=l9kX&f6!&}^cbbL5%W0i;lIQe0_UCIoOmb5;&ky7r zxpyKS9=j=-{mG{T&hXN5B`CE`htx&4jG;{DdF|XtpLuH)m4}wgN~zszP4f+$$@H7A zXR>}gB_uaXrRm}Lxu59XmWuQL`tzDbzLm|qubXlBv$}@H8IxkVN>j@tAR<)&kZY)l$B;W4c<#sOD}!(Sh#diZc=Cgk08e&H{QX#ZGuLOX=cNjJ$ypom!Q}gW3f=Ec<5m@s z1>I`NJHY!n3BM+%AC@eJ1$`$Aay7fVf$0}nYsV9=Tqzj<+roLgrIG4jhGfg8{s`kp* zzZYhuKAH5|d3WS2S5Y1#RyWqA881&C8+jdIUxis;_CI_WvKjYJk=#FxxWL&IV*%X1 zj%m5wWUE}*t;6n4w>O>4WIa6escCX%nPt=vVTIe7V;5&MJ{UK`kT8GB@IS0t?Ge_m z<{6}NKwMK(Q{*`JG(SL-)}sa^Au6hZnbOkHS`jC`_dL?!;d#^jL>3=^o!vUiW?e*} zBqfgx9>9T($qaaS2v8mSTRo+3sZn|65&JLZeCZSmr4$S2-)Ql0CD~FbbrCtaO+B^y zw{O2(xvIDSJ<8TvpbM5TZyf%B5wV%-|8nby`Qhb)X%9Tj$3qiQgK6JEEly+)6rq&_ zbDXfFGrts57rgrF>{T0hSgAYa!O0 zw$dKJ;fl?wr{bpj!r@N2yKoL=7CX6n41TAdFpXnldsGZQXTa-9CgWimf6JQo6__|6cqHgZCknW zh@5WH5 zSx(7cV{r{e?DOXU_|q(1D?J<%U-I}i3yWwh^yfHbC%u(-sTcKy1!dF!j39CQH6{9AWz9{e4irL4wQ4~jy4nXMh!({#$u zpH=C}JKy2@8PSpJuC&bR(A$q`Mml5Obx%cbp~Aw-hh+qLtQBB?u+}3ym9Zg1Ps4Z* zG#xg_eqmk9F{&)AV>zwEUeQI zk_)}qjhKs;Fy4;%l>%R}MkvCj+L=lkhiwW`l5lcz%2@9&t=n)?CO)zJk!8pXMVnP$ z+uM`(8`(UP@bmF8O`UylK^R7=>vGRfj_r(4=CeVSUakDlYd4s;^C&|}T~Xe5U;?Y8 zal!}D{5d=)vRG8CZYHoU9ChLo$uX@IUPz&^FWKhN^Rp;^#;rBE>v1PDbu233rI_C4 z;D4?o$Fh@^DW-U>#H!5mF2gTLWZ)Pbkj!lzU*rEb2k~i4f5bzT9LNC=ClX27wCu~@ zq+;S``vWo`b09n6H-G0tg9QqB7yg8{Yk&5~4@X7kZ&5lidQT@_HL$zUNALXh%`P2c zh1W<&G&20i=gto=t}cIhS#|8Af`Suyi{{R-#kiKTESE#Bm)<|SzJBZ0t*h#lF^O@3 zMfIZIOWwi?>oA&WX3Nos-+8Rxb`(yxc|xH-=ZbORWi{`81mi{`-Z93E2 z5Xj2B0v5_Fj?aBn;%hT-0;|ZWN{-mQojpwhr>b>b;gJ#dXvFIdpF~2jS2LMhfk!Kq zeY(Ao`WBvnk4WKr-EpjK-Mznm`y08U(d`*Bm|(J)Wq5TrZ)Xd; z;|8ofpR>s3k2i`&gIzex$HSCE#Pf+bmfK70Rv9=Q&ow-%gi~ZV_!z!u#Cv05JVd<1UFPJR9QQ&Fp^A!{D&Zhfe6$M}WNxksWrV)RRw zrh1(^b#802S>C=e<6!xi&us^Z!CwaQ!Zrnw7+_CbpghGG9<(v<#;rXgt>^I5XU@%a z-CW-x*<71;xp8@7>0HS@EL1~D+UKZW=Oi(NB|+hU{=`{BM&#G0A{K1Q;hsI|8JtKU z=ihvG#*6L((riv|g1VBzqP^FR!mQF8C%=G^d2KAc@6a_P5;@nMFxYPp?kn;NX}q7^X51F2U={FnQfv5Wmi5#9y1kK42$n zI6gbycaYe=^4HP_zk1DsM6%Uq<@}9sm(e8ITFqxs85=a2fwo=nv`)RM>94We1-kl1 z5ee-mD&AT4twgoE3bVJu>i^2QKVC^r`{p#qi7HLpb1r&nJCt=4t{ju0HssxZzh%B; zXLr2_Tto>aJNqc<*1pki#8A@5&u=Dj)anf2XyqXDZ&xQoE^weE+3kS;nG2po#WDYZ zSqPPsV#$pcCzT3avU7FAzBYpKTUyn5f5M8jxY@fg0l0goO!JtdU(H97B9>MHCpZ5D6*wG>9sScT$h{QzA zZ6`BcU3Tk#3!zcl?y|rY6m|n~vh2kFk!7=rksY?Gz8!sf`tq%_paNC7^^%RNEb^7V z|E4M)eEWAwJo=%JFimt9rXDWX@qpbm4n&EhYmt!w8Q;-fKh>f=4QvEoooFqI2F2_) zY|osQO;M_@B0s=8y#Q4Yy)#o4X9A>p{=tJ)ZJW?gx;=rjXFAN{IiQT!?b#Fg?ZXRg zcQe$o%A&O;+ggEY*xwsfkLt@;%nRS|o}d%$hf0ln!(q4n-a{7C$4;1D-lr3#sp9A7 zcYB3A(*KIbhcZveWg9FtnTDHqeCiTw8k~=|qJ7JG!d$iibGfkbf{C$N*qcQsbW74V z%gJC zcvD{TWU)rNaZvi6?%$2((3a=UB`12l#1=>vkbI?2=+?@ie;c|6wX!s2zZ#h*-8}P5 zh;QG>9NJfLe7;xxFD&&Fb|ty39-PakFLj8;)C1*)i`Yuz9JegHwrX9I$DlS#V9{xp zmN{w3-ubYg_h7OmKgFmlO&JY&RBdP5QRix(GZ=qL#QED?tu_y5_C($+jJef z5gM#!i;tmwFA(nB~`-DH9ZrGgB3PS(3wzK3S})xm(GtBp<8e7^M%&ns+w&$G$|k7 zx)`H)(})p?Wt24UsioXX3XdP3?6q$$5e-sy&6xbrr0nwdobK!?%N!HW%jnCB_aZIb z1-%W4kJ=R%s+$myEYw=5U2I?2QCPnEnIN-w8R=|fiJ@K3`Dg*pXAdOO6D9>oj1IV8 z+pvE1H8eI2IPSql(wZ~tn3Dtz4}r>fnb}+e=OWhu*+HU_df;G`G2)6ZS%p+n>6+#S zKBQODrh7`9N>|2beqA@WK?(Zq&DGek=}4&%wre4H)e9h_H><1Y#nCK#U-5h~cnHAdQ zUrSUc?6-i_pfls5a9jBa z{IS!Elu&w_yWII3yZKL3?<<0KmXQ};^t66{bN>qE>!wRfcmo>obu#Un>GmdatI>({ zy0LS~an3jTRgvrbr9Ge3mXUk2NB<6qp*w14D13>sbl(i zkH^%4H8xvRws2E6^Poz4aMx%rmzn7B-;eEB;Ve&0K94%@1WY^yL;Wu|bGv&TT|d6m zlXTqmC9*|7qdN}oO@LD~&TweE;U0IS7^iu9Qox?En*}CKD!@2+@i4dU=vQeNtmI3i zfC2zGKE-Le!trzXtM*Ts?PmZtT{BtMP|7Ov%dv*WMs){=l$UB1aflVFtE3Z^B_vk& zxa?PHBKFG;7;S?o=`9JMPTBPRbn5z1e|EK}OV4z`$9y4M!GEiLywZfjT5&f~*~C=i zu(i0Xub#=9SC&6Kaq5)JvrfI+M~a?)UtsHQlxD4RQNikC>Y?*7)$L}NpgFUJD->h@ zIJfm~h{2-rYX9or&QcK$8ebV?zvAMm1?W9Gp09B|6E?^%Y8RI@On71_=lWY(F}ydg zXR&^4zY~2imW}lg7m6Z>T(?YP-ZvHsB%EeE;?mvY%^oO2oLA^*=jFJ)?O)_FdSgNa zpj>eO7zSlb+I%;!#Twg|Ctz|OJ+hYM&6i(8X1+W}PqZH4k-YtGi>3;q(ErMjSR4SwN~}V`d(XuDppEWmGC6-miFJLtP2=r+or2q(Gv$;Z z)gl$5WG|g^rtC-&4!aCIbw%;1vu7`0sM9(OOZeHnHPMuwEpRe)Eo!xB+jk{%O|~PM zl0_Xt^~4r@agt#B>+h4G$` za{Mjs=()ADV~R~CIK^ zRlt+u0K%&;_>MhnRi9qmS_AXS>znn$JH$F`fy>ix$A>rYw}f#UHLdtlz`7)EYOP#u zM`5r09#5uwh~G>z@;EG>svp=PYMM>5_h9CJMLgeY&*Xm*a<39=&fG^IFSN)9s$itP zJ;Uy)r>E!qQV&7Q5El$7-CxJKx**$@Zm41OhccG*lsAXPJ5(3MW^@V5&TTop&j zHZPBQBfy!pGoKsPWp1UPn1CGNc}8GbP*9M~vyBS-O)m1NP09BVuU)oXHJu zPx{8|sA1}u$&bBCfMIw^ZQSb8ku;s z@-L*JDU^(y--01Yh`WzU{Ppcl|xh5i8a&jN^$(7v#}#i9N; zbYohIJ77@(_6S+vI$^a~b1kRb!p zuTFTQ^R=_SC<+wg_QZh8TNa)*} z4sWm)FsUOc@(WLN!$+Ja6}=gIVPPOJlKOY;dZ3JaNzjWtMiK)Bs%mg8LIVP3LD{6F z=gH&8=RlJ_eyR9l-JM2oGD>R5Fu$+OSI@SXj2#maw!+;QmdId7n3;v5qGEOXjWIVS z?^dK7Evm1tkAPF-wU4L_3kypKyLGT$?R|OlxxQq^6$x2-XdS}GR~L0zx=4l$-5$C5 zPgd}#dtO8+vDw`b4WBY#6?_5&1qI#c-=)Wq@$$S~!7l=f`7TDQoBX;3qrY0Ej|if) zW6vU#*6(0Dl4=gl`t1%qSB2qnDW`7{c`Z)rLkW{dm(`37?)(=sy7?UkE2d(l z+aj}esg*dv9js>iJ4Ti5C=`z}%F{0`chS3{7k48S8Vihwyt^tJZBOp}voj{V*sG=G zw`=$A{T6_Ua=(BoakMRarO3Qy!Y;jXO26WLedXYU%AP6d3oh^&s%sR1NP$U-LW&8cSf1E zK#ump{+D~GAz*@;pgn&3OGUr?p%2e_D-92C+&WOls{D{z^=t?WP>e{+>RF`sVQBe@ z_I$Ij>I}D)$$V04!s!gpP4d;Oc!@VL)lr{@WUBUR52&(u|#^c;iIb54e1 z9_1|Le?FuXu(g;}6=23!d8P?W^A?i#`|#m|R`K|XaD*{Qd!7oZx=v9HD&`X^tvCGb zAPtxMb|9GSd)~cJiNgLjA{FVwGFwL{ZXO=X*$UPq?KKMIr#MU|(2ADPWzqk#C!DDH zCK}|Y0F<+p_El~mO^45swAiCX6Q(JN9bw%;h!8b`c)rqk>W-9Hl4n9a(XbD?PmnL`WCU&7-5x=}qxF8`r6eFe# zdhVre26-X^_nJ3sZzad!(2_ao-Ke4e!y6UpCMPgkQ4Dq87r4d#PrYybc1&}XDGg#T zu1|hKB|T8|$RpU9dgTB0;>EyI7>a#B8HQEne1s zy6>>1WjwGG3Jr#5QG&W*!k^S8e9saF7m<61yUr?4BVuoL6e`~Nt(PsWiJsRA&HPRM z)kAN3C4WMAI1S=(Vj%JPA;K?2INf`(=VR}X8E@J&5RJ;&TRo3v4U@dBEg#7B+pQf= zb$vNtCKt$U1;hruJL9}RHsYqD)bQlsH34cSqtI6|Yg9Emm&?4$|2*43MrKMHde7s< zHw>0`f7@DXZ`z_GMY=4vIJMo-7Z(##HMdeL=>iO(0pgcs`4jRrx^A0SX{?Zxm)8ZAVt22(!q|{)Z+5@y)1o(E zK&y9(iI9Tj9GG;K&VcTG4&%dLRzus(ukYtzZc|-Fd=V69SKZzO(Z#(lGM#Q=_?9YH zV}gD{s7}10V(ceX8KDi?w?jL4!M)ovq^iP(y7Ty^Bqo4iY zq>OJYHea1Pl^EDV;{T6t-@{*}iDc&Ya=;p|Z`z$-+=PkY>RdDAlyvr`aZZ@fSx{6I zKvD$xeq;?1a2?${vsLP)GrJHsT(D^@WSh1F)IGm|fXEdBQ|u5MIiy3#{aX|9zdLx?-&lQ+C)Is&<8^(TkJJmRNB1!xcMDE-Z`r))s3j(_Pb9x z{n_6>)@@tS0j7dLN7m#PGMlOqw6r=HknuGgUIr1k{g@j?{+l1A7{& z`pC|m^LS;hCw!ivflk2awv0zUsy<&0?rxDq?;i$d$;^N0@&@u;4|8fPyAoz~J%+|A zTFhhYlW-?M1eO9Fw4~|Iu16CEp=Gzqo_pNK&?N`2JPBRC?bB7;rlKSSvc0X?o=3oG zobu+Kk`XUNMFC>cq!XN!;=;nR;^HSy&J!AJ^QE!lrih>rULK~c@*e)PBD=3?MgMG| z*2=@sEeisMCdy#N3Jcj zCkE{DBvca;&76>#?ypU|`O*QS3zF>KYqhoF;nWcnM!Bo->0Mil|7e8?$ zMhPv@m?luK#8FTB7JQcTbPl*85mE0jA@U9i&Z-zx(^6%L_#2n>U1wnM(W1D|D86L@ zmfC8S`t-R>Y&ANE~Jqc&~M%%C9*oepa7P8YiL!-)L z9(Ukt(qlqn$BZ_Oa-2SA8yMZx(;T>3fniF)|1vur_}pB13WDnxnsr4n?EgI*vYEzC zjr{5xH&!$$?<)}@vr-JqzgXCP|F9+6=uS$M^_MF|G0Fz(GBfG>*faF~Acge+r6?os*Mj zSCNP^;Z_q=Wxap6ls@76;E=Vz*5aGzKUddk*ic>j&Us6jS}TU?+VbRG5~J@VfJleM z6ZAsu#WWq@gaYc~n>YN+TxrnrRk2UuP|ATeVk0R9pggM9usyZ7NnAo=zslp)`N&tV z;eGMOMMr%>*}KEkx+9R&?PZor>dPuzs%!53eG%W6yL>;Oc=$(R=C`u-TWkCfRIDXx ztg~*o?mx~sRiE|a-O9zdjzO5;eN$G4rvvG2a{hX3p@m!bdr2p}yGH1N$yg{=w@Xuv z*`oCUsxxp&9UBtd7SX-X)_vMihYi32NT8muy&?$(X+K}z%WN0MPM2QNi@9Zf0G1rD zxWL&;%&bg8=7QModW(~4ItmMu%M|`O6`taev{_-f_Y;OKB zeF#w385$Y}Ri^AMo<5hSw(|#cnSWA(E2;p;nP}g?;9x zlt!!<*pg~#kBlqadN-DzaD0`6C(vx$r#O~S-vT9(YJ7~ z9!)z`dwIoj^U66-XZc~?5+eo)Bf&yCOzGSX>6Y3jz4pMsY(eL4S^v*=x1M{4L=ner zb#w?aghQKGcM{vSg&0(;QCp;LMA{escy`+KgS?^EsmxXk1M--*}Mczs^e z(n+ID`>!B47)i&gJ#5>R*hMt2;0i1r``c|JfM(rJ(Fq)O10QhCLK4#$24~77puKznq>H-%spZEA@5X;G7!i&c^I{n?{(C}( za4r2a5a{y8eFTdjd0wyBYh_lHCKDoK2Q$4p1jJ*>zg>Cs0}Q@M4;ShYwh(>bBw&V= z>~BbLHMT1TB$mwvfPT!# z>qelK!SDBhr2Lb=U%vm7gnnvK?K&dj|-+wMv$g%$z6LLXDUnA7HWwsM)b90T9!isO6zfc4O)G9phtQ}?i z|9rwWPkycJe*Uf?H!HPpPHxAICw7+@EEUr$EO*b3{MFYgtj3$16PxDZgL*uoDziuS+ zSM+{9DNZGKG;()W-`>CZ;G1?ua6x#0`&L1c`a42J1M$ z7{_5U`~P9@zvFt||Nn7ZPelqX6-6RMsE`&7k=>96m4;)aq-aQ5PgD+)J=zpf8l6dL#4fc*ZZ@fXXiNQc)!j$pWo&3J^w{KpO44=e%rU}^>*W2zl%w1%V0T7 zh`XdY#PiroEiTD51adNr*-*gt>C>mxhK7D%3O$dt*k;-Lk3OT4^*{3SQO$bX_ONZS6sJ>W=aw;UCHammE8v5+};hc-=k~vz$Li}TfgqWm&xQ{gx512nv-$l>UR{3vL;0P&I0^O z3l@K+m~OHHLtZ^Z%6=Hj;uj8^+_EYh658fXVe%YfM4=+?#8woStWpT=R&KXlE2&UmOk|0Tqhu9eu|cxX?YD8O z3BSYZUh!T;1SID33!2U`tX4ZO@mb$2OVKxSJ0}B{mjvaV*aYb zmhUx!Vv1so)YuR$AI5t7AFIH?Z{0ju+MG^$9?#0*vc3d4_T)YBWqOGSO~NE*%CJL9 z7N&I?OK4wSJe}B{q@oJf{z}2T#vEY&B{1G19B9S0s4+Zyud!KU-n@Ca>BsgYl#bek zt#X@_N|*Yw%f@CR15Ck2pH;7Gv#%W9wr$%R!w#j_G1bk_xE$7V6RD+1F@0ThY2}(# zgU@(_1udi*;^OK+EPmcKn$LR#dc;M4hVwkzJhhm^aOeX|IABuReVI`0;ZzxlpPWH$8Ca zl^L2;`m_!LjKf09oJ5;+^1!G_Juo%cb&5 zE-8Z2I@h<8@!iU1ACc=_EtKKV`5}vMXqjN{{!l21=`;Ppdg!`rgQ64<`J`jWJvUY{uldFb$Ip z&f+ItTkcA>$2 z#@Bc5+qZAOVW`a$)F3_wy$|(+HT(lXxBBQS{<_e!12wB;v-k+(Z1Nts$6=n z)BP1)*xv&*JZgX5*}Neq3=3t?A6=cbx8`g45~Y9t6;X6N58}v?E$Y1Xio0@Vq_rg3 z*PcOF&;7%y%>wcYBa-G>w0x9Pk6N^>!svbMtx0qpnn!eK=9=L-Un%RG<_g);7azne zS>@x`%~W}D*z2p?^aZNJ?`4?o5B~8n8!Y1E3nzaSDUpLk445m&Zhv}2np^GFt#>9c zeh8Ht6s70oZL4zEbl!tM(7L@< zwYKhtM&Jb$+G^_tvE9i|@AM)KH|I_*7Ew|%7B@@?Qm}1!$iL2=jrSA5YLnR$XZ-!= z6R6F5r^c6Bn6Vk*<@goN_(qGUR0z7#Cm=pZvEkT0O^Y-v8vcyOlgOLzH%iw#yvpv= z6D~QxC0ka>k+PJlN}hyYVz~6_e7Q|&AZUF9ao@bL6EN5Lm<98zbUciMnH^he8>T&M z2Z(DQ5M3Lb^U>G$k+u{gFiaG#JZfgW53-3}l~>fLkn4ouBvZdisaftUs6K!5cz;0t z4%d0VW#-mHjJ7YF&Lj0}Q7arjfCXT{EHc;{MztLJK_#Zd4win5v1OhN{`oUgmS66% zwz1-FcaLTiOAvGs3DGf!Sl}!g1Pab z{`*`2DpBo@4=KyOvV0+61e=o`_Wo*_Yvc@y#db`#)QU7r+*+GHMqY1O8^*Ytg47x- zDJd!%Vd()o-BY)X4g3W+SL*(Xp2xcdBljr@-M3&f!$G2VL1?Ec4E0h*ePcA=gEoZD zL=4Poux{8aPZ>`XG5XMgEzC}2!b99h#-N*#-%G4M>fXgbR^35+&VLn)V=HPU>WOms%+q~Z8910<^4ddkXsGs1q#~p~Y%1+cj ztbe@jRpZf@STuY$6X zE{@*%9;~WKQf8;N?bzY5SmJZA`8Aa!5rydyF;&LyZ%{<~z%_0$fxTjo^KBcj8A;!D zrbpuF07<7d>-n4tk~PV9gBk%_dg8yoXim6pL{5^nQB~)fJV{;8Em3;6znRbh8HAb| zI8FNN2}aK1?^0-gtR4(u_V1-XZr(8BFYSljSp6Zwmet&z4;#FaxyCWD*epAdtwU(h zjuTT~;>@q1sQEAfd!R`ZuIXShYRQi8Kz-OWUTA4$YckvO02!-*o;T_4?6Jz#$;nF< ztPK^20As-0t3tI)lFPKTwC<)EOtKX+SNc?5z7|xR?n9X)-^uLCSs@^0CLHMW?blBg z6`cxOFnnRd{f~S1?%iH!4~W)|6P=Hzx~>Mi;JEwUxvkQcg-_ZWD^&9|UcPOu%(-@) zO#A(~c!hoi=;hDz^o(m|PO^>FGi@;E@Kbcw*Y-7;R!b~5*70g9>A8Dj-pzX}i7-dP z->9wzjxnE7eVkHvTwOcL9nPvk##3`y1q%zyLco1D-`?u(a#BDP{dYm73-qg!GTlv7 z12Y3~Redu9hS(;D%_LI|XJkA7^H-HNZ&`&{lRnECB``=x|5E9~nM2r^_(2REyzF;j zoaVeHk%t1Dz0ov(f+d;Vhtfbc6`8Mpx8CB0N~{je{?+Y1Na4oY+`xS%14T{m9+|!c z9)?q=dgi(dW;SE^WFseo^bkDSCY)9cTIt%lqLl9PH1j;MFIX9IInMuW83aWSD@d#L zlyo}L#A$zNg~3r*)4w=-qe%A!2(iogTzmW9~Yu;xxo%(@U+^n(bl)+H-kb0=@I|M~ORln6qBAlb8BQhPcqcA<{l z^B&l|dHAc>ln2L8T91s57%xwt8#V9ZHQ=TBZ0A#{WyEDirFN|S`0?YhSYV^ByvMY| zjxsSxL-+d^KTFye>bwSlENvI2W~V(+{)%Qg83EJWVy*$fS*@Mm9I{H*P7#zZKtnRD z(n1SnVmT|jydel?#YOD4Kt#ob1~pwRLx6W$OebtYbv2@v9N1X+$rZm^9!8J1ZZz+S zx=wHAQ0I`I1#YCrq&0nA;k#zX?N7IUp;y>x0O{?Fw%m1TB7;9DSxc(s5_A8?3Q8A6 z`rWX+k;V>2Y_rPw%_0=miCodu&OHQx9(f!2!yUjDfC|`qTO1LP1n}8;Qwd>!Y(tyo z=>sqg1H3udb&C3kIP=|h+3hIOguw00jGQTH?S#JhzIT@KES987a8o}vtj7=lsZYca zY+UnVvR8*ixRr9tzE_YyI4HMat|r$S#jk*&?@)m3-G z@Us#Glm(7%(t@o=$jB5&LEh<6>80yBMvt1la#1NWDiiBB3G~f^ki}%7vE#+~HZnpi z4KZeELzyZgjAw|?ALd#Z{`f!CS*)ZglL1S}iP@=_rP@=Ch3MJ7$5=>5Ut+C56M1#CfP0Vsad}R$QxBX)-$&SYn2UmlquV{OQCHQc00b5%bOTWtV{4X<*!%)YK@3xeM8mm-K=)dLl` zRQYL_IOK)tG}{AKBn*{6nRgpE&A2_13 z&bX7LDahtCP=65o`a==cq57Zzf%PjrX&l~yDT+`Mdc2Qfe)vOLDM*X1UR{7oBg z-I1`@S7Tis(Q6vfo~YZ^Y#6>o2u`)~213t!KKjsaU2T@Y?*B0{Xc4L3v)~8un1%yQ(cToHZ?Rf=m+%@ z)VC5CgprSm67~j57lZRuUF*RyRh&#ne)Bktr^Yemj(JKGeZYo^1}boGV#HstF<5Eq za5^{X0z;RU!51>fJ3~JGqc#eY?;p2OP^68{gW01OV3$TqVq<;>Y_HL8HTp6R681({d+yHqJE(-8!xYYsg+Ei ziSReKc{=lAWv%w=ZbC4tIzSzlelsTQCfnRr1tFI<)A{R@Q7)syz$)0-*{>NU?(6vF zS}l9=*}NjXPA6pTBsn1kDdXa*&Rr!mDuLxzfL3znVC$aw+qwxl7qHv(Iz!&8sF<`K z^|~c{KeAx%-2AnH^q`%v^bYs2^oE`nK*&JN>-=r4%z7@A)!&Y1$P)r|O` zloSdt&a5<)n~X>qSnHZq^pOb2VrC(Ab@9#uuM~Brt4eWp)w;XTGgzHTh{DoVMe|TF zJcUh;>mc(Xz_oP9l`@-zKdK^ChT7yMUlW!~?|!5CF#*#O&YU|JqNI`C?mBzc`b)wR zDNSd-e*3V$cnz`!mHavssm>u$`eI3D8$x1Fwi(R!lLsY4w>;B7yfwwTRArLfzHPt$ z(hcb2H(%HOxbxM!aN!<;W@TTXcW^0?VP?i+lHG*ji_80x#W7XU5WDNHy4GUKWzSx> z6DP)YC!zqUdT!k-VxZo_%*R#y;tb!&Yr+3$6}g9_k+3^gt6!QioEUIKWMvOybV)#{@~0+sx4hd9X`Ch(=-jgT0EXQ*NmtcrD0ScM zu`?Ob(jUJ+>;S0zCf}xr@w^v^Jt3s!Lnjpw8>S`l*!E1lPE-E6A$aA}LoDnn`Gb(} zm*D){NyO`Fc>T2|I-Aj!r19s4{~vnqp#_ zA72pPA_n6$ZfyrL*rRMmGhx01l`w8o$~<>H+lx7LpIq{_{-9q_W3M%tz7d)5kuA;935aifuR~L)_glhVq8xt> z`qh6El6x1^5U744gYmO(qp30z1*6u3;*t(&eL!%>6t&RUFy}8x1$hCw2a_sUd2J&L zNjeQAXSqz^9gp-?Cj>u|{OEaLt74UTvp)zZE1{LP76{^#wsFk1vLS&-HMf;)jp%sqQ~k|@H??T-p8>3{+Z!9<2vSbZ-&UBxn|s#gO_dma_Q zB|EuZ3`73W7SKacvA;1CWp(`JPjLmvcMUI-xe0`Qsub|>IRA5O&ZutPlog=x`7np# z>_?%8Ug?-zW82>{&P#V(HAu%mR-c3U>hQmDDBSt*E$6`XYHH_6`|V&QN}U%_1eG%T z!p*}obIFpV2Y0w_dwR{Zj~)PPb~`@P#Yk*ZDnc_Y%Q>2DrET!SiZ9=5}gy>e?;zo;F zNh68ATFlYgYI=>HL@!db5Y^*+g*_7T9^ZECevCCTXbw={qMo}ulwQq9kcAgP%-&jFPpd=%Senim9q1oS_ zo)xXGEr>7>1w-^znxcKprMu>NV1(~WuZkIfe7AH8m4ZdkLME#4&8Rz=3|3wam`>4; zWRgw~^(TJQf_hwU^;|iPwx!%J=`ZLwjqkA*4a3FI3X*0}1z;=^F)^(WX2LLOCEvi{ z+1tEil4SR!DGXupkO&xbFvY-MPKu-;32X*B>z2$8X69Sm!j&(@Hg_1K23JE#XV3HjB3*Bku_s3(OoL!YR ziG-efNJ1; zO=|%rx!Xr{=FByiAk^~4jcMdI%(9&pq3saMc3T&=Ltz^r%+~?)UNrYHiiHunfrvFm zO)+BeAQ2gqlCMFL&0ZzJ4imRpW94`3+&OxRUVHcfQ6w&)ScibA-#{uGOVUnU;qvxl=XX0 zgz&&eP2W7y@(0Ka>S|QH1%B17k7KBLyUk@mj|SY&Ee5_FrIi5FZb?+<8(>}B##G0Z zH8qpyn1k=~@ck#6A-T**(G=hn?Z{=uT-(Wfiw~D+ZrVYQ62ISk^kH(Dnk?l6aY2Bi z4OO8ds=-K6Z>fDJ3a;B(GDQJxp`h+~6?ZnS^Nr}m5y(fZdEOa$yDFe64YL~wK^5~u zWBGKAz_O>$(uB}*d8wB5vidFAua3@CXkHY0s^JFU0E8e^zQA=}cW3O4p5`dmU7cd0 zqN{qad}<9!ubh9;v3h&T%zS&JW=MjN$YwHjviWJ?r=C%D|MWWH$Yuqos z!_mIKJbtFBFO_IXCS(j9E%-B#g%{?S>HnZ9E3AQ8d zuclF?h1WWqSmQQhf>>oeo7(Q`kOz^Oj%ZhFuu0_mx{464`gT95$dxmdgPJKV;rYG-rhyin=3WPfw4a`nYvJlNVNY8@d;Zz8oRQoRX

Hello, world!

', + url: 'https://example.org/initial-post' + } + ] +}) + +fastify.get('/websub', async (request, reply) => { + console.log('content provided') + + return { + topic: 'http://localhost:3100/auctions' + } +}) + +// Run the server! +const start = async () => { + try { + await fastify.listen(3100) + } catch (err) { + fastify.log.error(err) + process.exit(1) + } +} +start() diff --git a/mocks/auction-house/publisher.js b/mocks/auction-house/publisher.js new file mode 100644 index 0000000..c760820 --- /dev/null +++ b/mocks/auction-house/publisher.js @@ -0,0 +1,17 @@ +const axios = require('axios').default + +// Run the server! +const start = async () => { + await axios + .post('http://localhost:3000/publish', { + 'hub.mode': 'publish', + 'hub.url': 'http://localhost:3100/auctions' + }) + .then(response => { + console.log(response.data) + }) + .catch(error => { + console.log(error) + }) +} +start() diff --git a/mocks/auction-house/subscriber.js b/mocks/auction-house/subscriber.js new file mode 100644 index 0000000..ce27197 --- /dev/null +++ b/mocks/auction-house/subscriber.js @@ -0,0 +1,42 @@ +// Require the framework and instantiate it +const fastify = require('fastify')({ logger: true }) +const axios = require('axios').default + +// Declare a route +fastify.get('/auction-created', async (request, reply) => { + console.log('subscription verified', request.query) + console.log(request.query) + return request.query +}) + +fastify.post('/auction-created', async (request, reply) => { + console.log('received blog content', request.body) + reply.send() +}) + +// Run the server! +const start = async () => { + // subscribe to the feed + + try { + await fastify.listen(3200) + } catch (err) { + fastify.log.error(err) + process.exit(1) + } + + await axios + .post('http://localhost:3000', { + 'hub.callback': 'http://localhost:3200/auction-created', + 'hub.mode': 'subscribe', + 'hub.topic': 'http://localhost:3100/auctions', + 'hub.ws': false + }) + .then(response => { + console.log(response.data) + }) + .catch(error => { + console.log(error) + }) +} +start() diff --git a/tapas-auction-house/pom.xml b/tapas-auction-house/pom.xml index 4b9cbb6..df44681 100644 --- a/tapas-auction-house/pom.xml +++ b/tapas-auction-house/pom.xml @@ -58,6 +58,17 @@ validation-api 1.1.0.Final + + org.json + json + 20210307 + + + org.springframework.boot + spring-boot-devtools + runtime + true + diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 8fc22d0..3459cff 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -7,6 +7,7 @@ import ch.unisg.tapas.common.AuctionHouseResourceDirectory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -26,17 +27,21 @@ public class TapasAuctionHouseApplication { public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); - // We will use these bootstrap methods in Week 6: - // bootstrapMarketplaceWithWebSub(); - // bootstrapMarketplaceWithMqtt(); + tapasAuctioneerApp.run(args); + + // We will use these bootstrap methods in Week 6: + + // bootstrapMarketplaceWithMqtt(); + bootstrapMarketplaceWithWebSub(); } /** * Discovers auction houses and subscribes to WebSub notifications */ private static void bootstrapMarketplaceWithWebSub() { + System.out.println("HAHA"); List auctionHouseEndpoints = discoverAuctionHouseEndpoints(); LOGGER.info("Found auction house endpoints: " + auctionHouseEndpoints); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java index da2b096..8066010 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java @@ -1,6 +1,18 @@ package ch.unisg.tapas.auctionhouse.adapter.common.clients; +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.http.HttpStatus; +import org.springframework.stereotype.Component; /** * Subscribes to the WebSub hubs of auction houses discovered at run time. This class is instantiated @@ -9,7 +21,23 @@ import java.net.URI; */ public class WebSubSubscriber { + // TODO get this somehow from properties file. But on clue how to do this with static variables + static String WEBSUB_HUB_ENDPOINT = "http://localhost:3000"; + static String AUCTION_HOUSE_ENDPOINT = "http://localhost:8086"; + + Logger logger = Logger.getLogger(WebSubSubscriber.class.getName()); + public void subscribeToAuctionHouseEndpoint(URI endpoint) { + // TODO decide with other groups about auction house endpoint uri to discover websub topics + // and replace the hardcoded one with it + String topic = discoverWebSubTopic("http://localhost:3100/websub"); + + if (topic == null) { + return; + } + + subscribeToWebSub(topic); + // TODO Subscribe to the auction house endpoint via WebSub: // 1. Send a request to the auction house in order to discover the WebSub hub to subscribe to. // The request URI should depend on the design of the Auction House HTTP API. @@ -25,4 +53,61 @@ public class WebSubSubscriber { // - W3C WebSub Recommendation: https://www.w3.org/TR/websub/ // - the implementation notes of the WebSub hub you are using to distribute events } + + private String discoverWebSubTopic(String endpoint) { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(endpoint)) + .header("Content-Type", "application/json") + .GET() + .build(); + + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == HttpStatus.OK.value()) { + // TODO decide with other groups about response structure and replace the hardcoded + // uri with response uri + JSONObject jsonObject = new JSONObject(response.body()); + System.out.println(jsonObject); + return jsonObject.getString("topic"); + } else { + logger.log(Level.SEVERE, "Could not find a websub uri"); + } + } 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; + } + + private void subscribeToWebSub(String topic) { + HttpClient client = HttpClient.newHttpClient(); + + String body = new JSONObject() + .put("hub.callback", AUCTION_HOUSE_ENDPOINT + "/auction-started") + .put("hub.mode", "subscribe") + .put("hub.topic", topic) + .put("hub.ws", false) + .toString(); + + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(WEBSUB_HUB_ENDPOINT)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .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); + } + } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java index d156452..9e7a356 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java @@ -1,6 +1,10 @@ package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub; import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler; + +import org.json.JSONArray; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** @@ -13,6 +17,21 @@ public class AuctionStartedEventListenerWebSubAdapter { public AuctionStartedEventListenerWebSubAdapter(AuctionStartedHandler auctionStartedHandler) { this.auctionStartedHandler = auctionStartedHandler; } + /** + * Controller which listens to auction-started callbacks + * @return 200 OK + **/ + @PostMapping(path = "/auction-started") + public ResponseEntity handleExecutorAddedEvent(@RequestBody String payload) { - //TODO + // Payload should be a JSONArray with auctions + JSONArray jsonArray = new JSONArray(payload); + for (Object auction : jsonArray) { + System.out.println(auction); + // TODO logic to call handleAuctionStartedEvent() + // auctionStartedHandler.handleAuctionStartedEvent(auctionStartedEvent) + } + + return new ResponseEntity<>(HttpStatus.OK); + } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java new file mode 100644 index 0000000..9e25a69 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java @@ -0,0 +1,36 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * This class validates the subscription intent from the websub hub + */ +@RestController +public class ValidateIntentWebSubAdapter { + + @Value("${application.environment}") + private String environment; + + @GetMapping(path = "/auction-started") + public ResponseEntity handleExecutorAddedEvent(@RequestParam("hub.challenge") String challenge) { + + + + // Different implementation depending on local development or production + if (environment.equalsIgnoreCase("development")) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + String body = new JSONObject() + .put("hub.challenge", challenge) + .toString(); + return new ResponseEntity<>(body, headers, HttpStatus.OK); + } else { + return new ResponseEntity<>(challenge, HttpStatus.OK); + } + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java index 9e6ec67..01350d3 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java @@ -4,12 +4,16 @@ import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort; import ch.unisg.tapas.auctionhouse.domain.Auction; import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent; import ch.unisg.tapas.common.ConfigProperties; + +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 java.io.IOException; +import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -17,6 +21,8 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; /** @@ -30,8 +36,38 @@ public class PublishAuctionStartedEventWebSubAdapter implements AuctionStartedEv @Autowired private ConfigProperties config; + @Value("${auctionhouse.uri}") + private String auctionHouseUri; + + @Value("${websub.hub.uri}") + private String webSubHubUri; + + Logger logger = Logger.getLogger(PublishAuctionStartedEventWebSubAdapter.class.getName()); + @Override public void publishAuctionStartedEvent(AuctionStartedEvent event) { - // TODO + HttpClient client = HttpClient.newHttpClient(); + + String body = new JSONObject() + .put("hub.url", auctionHouseUri + "/auctions") + .put("hub.mode", "publish") + .toString(); + + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(webSubHubUri)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .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); + } } } diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index e9c609f..96e231c 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -6,3 +6,7 @@ websub.hub.publish=https://websub.appspot.com/ group=tapas-group-tutors auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ tasks.list.uri=https://tapas-tasks.86-119-34-23.nip.io/ + +application.environment=development +auctionhouse.uri=http://localhost:8086 +websub.hub.uri=http://localhost:3000 -- 2.45.1 From fdb7d2bf6403c1715e0a994e75bcd959794e702e Mon Sep 17 00:00:00 2001 From: Ronny Seiger Date: Thu, 11 Nov 2021 11:03:53 +0100 Subject: [PATCH 52/94] Update build-and-deploy.yml Removing old containers during deploy --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2c5b192..ee925ca 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -78,6 +78,7 @@ jobs: cd /home/${{ secrets.SSH_USER }}/ touch 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 docker-compose up -d -- 2.45.1 From 32ecd6a5d4a2b00f16444dec77c92e4c1c53ae7e Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 11 Nov 2021 11:32:10 +0100 Subject: [PATCH 53/94] Adding a MongoDB repository and extending the use cases for adding new tasks and retrieval of a new task --- docker-compose.yml | 33 ++++++++++++++++++ tapas-tasks/pom.xml | 9 +++++ .../tapastasks/TapasTasksApplication.java | 6 ++++ .../mongodb/MongoTaskDocument.java | 32 +++++++++++++++++ .../out/persistence/mongodb/TaskMapper.java | 30 ++++++++++++++++ .../mongodb/TaskPersistenceAdapter.java | 34 +++++++++++++++++++ .../persistence/mongodb/TaskRepository.java | 14 ++++++++ .../application/port/out/AddTaskPort.java | 9 +++++ .../application/port/out/LoadTaskPort.java | 10 ++++++ .../application/port/out/TaskListLock.java | 11 ++++++ .../service/AddNewTaskToTaskListService.java | 8 +++++ .../application/service/NoOpTaskListLock.java | 18 ++++++++++ .../RetrieveTaskFromTaskListService.java | 11 +++++- .../unisg/tapastasks/tasks/domain/Task.java | 26 ++++++++++++-- .../src/main/resources/application.properties | 5 +++ 15 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java diff --git a/docker-compose.yml b/docker-compose.yml index 0081933..e69f3c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,3 +70,36 @@ services: - "traefik.http.routers.app.tls=true" - "traefik.http.routers.app.entryPoints=web,websecure" - "traefik.http.routers.app.tls.certresolver=le" + + mongodb: + image: mongo + container_name: mongodb + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: 8nP7s0a # Can not be changed again later on + volumes: + - database:/data/db + + dbadmin: + image: mongo-express + container_name: dbadmin + restart: unless-stopped + environment: + ME_CONFIG_BASICAUTH_USERNAME: student # Access to web interface: username + ME_CONFIG_BASICAUTH_PASSWORD: studious # Access to web interface: password + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: 8nP7s0a # must correspond to the db + ME_CONFIG_MONGODB_PORT: 27017 # Default 27017 + ME_CONFIG_MONGODB_SERVER: mongodb + labels: + - "traefik.enable=true" + - "traefik.http.routers.dbadmin.rule=Host(`dbadmin1.${PUB_IP}.nip.io`)" + - "traefik.http.routers.dbadmin.service=dbadmin" + - "traefik.http.services.dbadmin.loadbalancer.server.port=8081" + - "traefik.http.routers.dbadmin.tls=true" + - "traefik.http.routers.dbadmin.entryPoints=web,websecure" + - "traefik.http.routers.dbadmin.tls.certresolver=le" + +volumes: + database: diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 3eac732..7dcf6ae 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -27,6 +27,15 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.data + spring-data-mongodb + 3.2.6 + + + org.springframework.boot + spring-boot-starter-data-mongodb + org.projectlombok diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java index 2675391..78a6145 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java @@ -1,14 +1,20 @@ package ch.unisg.tapastasks; +import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication +@EnableMongoRepositories(basePackageClasses = TaskRepository.class) public class TapasTasksApplication { public static void main(String[] args) { SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); tapasTasksApp.run(args); + + + } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java new file mode 100644 index 0000000..442e01e --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java @@ -0,0 +1,32 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@Document(collection = "tasks") +public class MongoTaskDocument { + + @Id + public String taskId; + + public String taskName; + public String taskType; + public String originalTaskUri; + public String taskStatus; + public String taskListName; + + + public MongoTaskDocument(String taskId, String taskName, String taskType, + String originalTaskUri, + String taskStatus, String taskListName) { + + this.taskId = taskId; + this.taskName = taskName; + this.taskType = taskType; + this.originalTaskUri = originalTaskUri; + this.taskStatus = taskStatus; + this.taskListName = taskListName; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java new file mode 100644 index 0000000..0af73b6 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java @@ -0,0 +1,30 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.springframework.stereotype.Component; + +@Component +class TaskMapper { + + Task mapToDomainEntity(MongoTaskDocument task) { + return Task.withIdNameTypeOriginaluriStatus( + new Task.TaskId(task.taskId), + new Task.TaskName(task.taskName), + new Task.TaskType(task.taskType), + new Task.OriginalTaskUri(task.originalTaskUri), + new Task.TaskStatus(Task.Status.valueOf(task.taskStatus)) + ); + } + + MongoTaskDocument mapToMongoDocument(Task task) { + return new MongoTaskDocument( + task.getTaskId().getValue(), + task.getTaskName().getValue(), + task.getTaskType().getValue(), + task.getOriginalTaskUri().getValue(), + task.getTaskStatus().getValue().toString(), + TaskList.getTapasTaskList().getTaskListName().getValue() + ); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java new file mode 100644 index 0000000..beabf63 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java @@ -0,0 +1,34 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TaskPersistenceAdapter implements + AddTaskPort, + LoadTaskPort { + + @Autowired + private final TaskRepository taskRepository; + + private final TaskMapper taskMapper; + + @Override + public void addTask(Task task) { + MongoTaskDocument mongoTaskDocument = taskMapper.mapToMongoDocument(task); + taskRepository.save(mongoTaskDocument); + } + + @Override + public Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName) { + MongoTaskDocument mongoTaskDocument = taskRepository.findByTaskId(taskId.getValue(),taskListName.getValue()); + Task task = taskMapper.mapToDomainEntity(mongoTaskDocument); + return task; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java new file mode 100644 index 0000000..867671c --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java @@ -0,0 +1,14 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TaskRepository extends MongoRepository { + + public MongoTaskDocument findByTaskId(String taskId, String taskListName); + + public List findByTaskListName(String taskListName); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java new file mode 100644 index 0000000..b87795d --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java @@ -0,0 +1,9 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface AddTaskPort { + + void addTask(Task task); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java new file mode 100644 index 0000000..acca3c0 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java @@ -0,0 +1,10 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; + +public interface LoadTaskPort { + + Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java new file mode 100644 index 0000000..802abba --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java @@ -0,0 +1,11 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.TaskList; + +public interface TaskListLock { + + void lockTaskList(TaskList.TaskListName taskListName); + + void releaseTaskList(TaskList.TaskListName taskListName); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 2380fcf..d78de83 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -2,7 +2,9 @@ package ch.unisg.tapastasks.tasks.application.service; import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand; import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; @@ -17,11 +19,14 @@ import javax.transaction.Transactional; public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase { private final NewTaskAddedEventPort newTaskAddedEventPort; + private final AddTaskPort addTaskToRepositoryPort; + private final TaskListLock taskListLock; @Override public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) { TaskList taskList = TaskList.getTapasTaskList(); + taskListLock.lockTaskList(taskList.getTaskListName()); Task newTask = (command.getOriginalTaskUri().isPresent()) ? // Create a delegated task that points back to the original task taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), @@ -29,6 +34,9 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase // Create an original task : taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); + addTaskToRepositoryPort.addTask(newTask); + taskListLock.releaseTaskList(taskList.getTaskListName()); + //Here we are using the application service to emit the domain event to the outside of the bounded context. //This event should be considered as a light-weight "integration event" to communicate with other services. //Domain events are usually rather "fat". In our implementation we simplify at this point. In general, it is diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java new file mode 100644 index 0000000..783dca1 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java @@ -0,0 +1,18 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.springframework.stereotype.Component; + +@Component +public class NoOpTaskListLock implements TaskListLock { + @Override + public void lockTaskList(TaskList.TaskListName taskListName) { + //do nothing + } + + @Override + public void releaseTaskList(TaskList.TaskListName taskListName) { + //do nothing + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java index fd6aea5..5e5ec29 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java @@ -2,6 +2,7 @@ package ch.unisg.tapastasks.tasks.application.service; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListQuery; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase; +import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; @@ -14,9 +15,17 @@ import java.util.Optional; @Component @Transactional public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase { + + private final LoadTaskPort loadTaskFromRepositoryPort; + @Override public Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query) { TaskList taskList = TaskList.getTapasTaskList(); - return taskList.retrieveTaskById(query.getTaskId()); + + Optional task = taskList.retrieveTaskById(query.getTaskId()); + + Optional taskFromRepo = Optional.ofNullable(loadTaskFromRepositoryPort.loadTask(query.getTaskId(), taskList.getTaskListName())); + + return taskFromRepo; } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index b664a64..7a02976 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -49,17 +49,39 @@ public class Task { this.outputData = null; } - protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { + //Constructor from repo + public Task(TaskId taskId, TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, + TaskStatus taskStatus) { + this.taskId = taskId; + this.taskName = taskName; + this.taskType = taskType; + this.originalTaskUri = taskUri; + this.taskStatus = taskStatus; + this.inputData = null; + this.outputData = null; + } + + + public static Task createTaskWithNameAndType(TaskName name, TaskType type) { //This is a simple debug message to see that the request has reached the right method in the core System.out.println("New Task: " + name.getValue() + " " + type.getValue()); return new Task(name, type, null); } - protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, + public static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, OriginalTaskUri originalTaskUri) { return new Task(name, type, originalTaskUri); } + //This is for recreating a task from a repository. + public static Task withIdNameTypeOriginaluriStatus(TaskId taskId, TaskName taskName, + TaskType taskType, + OriginalTaskUri originalTaskUri, + TaskStatus taskStatus) { + return new Task(taskId, taskName, taskType, originalTaskUri, taskStatus); + } + + @Value public static class TaskId { String value; diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index fe25873..badc0f8 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1,2 +1,7 @@ server.port=8081 +//spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ +spring.data.mongodb.database=tapas-tasks baseuri=https://tapas-tasks.86-119-34-23.nip.io/ + + -- 2.45.1 From b4efa1ee545b2d0b365a9e5a41a45ba7248c9132 Mon Sep 17 00:00:00 2001 From: Ronny Seiger Date: Thu, 11 Nov 2021 11:47:28 +0100 Subject: [PATCH 54/94] Minor change in dbadmin URL --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e69f3c1..84b12fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,7 +94,7 @@ services: ME_CONFIG_MONGODB_SERVER: mongodb labels: - "traefik.enable=true" - - "traefik.http.routers.dbadmin.rule=Host(`dbadmin1.${PUB_IP}.nip.io`)" + - "traefik.http.routers.dbadmin.rule=Host(`dbadmin.${PUB_IP}.nip.io`)" - "traefik.http.routers.dbadmin.service=dbadmin" - "traefik.http.services.dbadmin.loadbalancer.server.port=8081" - "traefik.http.routers.dbadmin.tls=true" -- 2.45.1 From 3f4f2f4a1bcd8974fdd59ad8059e4ae788634af9 Mon Sep 17 00:00:00 2001 From: Andrei Ciortea Date: Thu, 11 Nov 2021 17:16:09 +0100 Subject: [PATCH 55/94] Update BROKERS.md --- tapas-auction-house/BROKERS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tapas-auction-house/BROKERS.md b/tapas-auction-house/BROKERS.md index 7e0d37a..80eed02 100644 --- a/tapas-auction-house/BROKERS.md +++ b/tapas-auction-house/BROKERS.md @@ -14,10 +14,8 @@ Running this WebSub Hub implementation requires Docker, Node.js, and npm: ### How to run ```shell -git clone https://github.com/hemerajs/websub-hub.git -cd websub-hub -docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no tutum/mongodb -npm i -g websub-hub-cli +docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no mongo:latest +npm i -g websub-hub websub-hub -l info -m mongodb://localhost:27017/hub ``` -- 2.45.1 From f652a9ecafbbb565a649a41c47c6eb891cdd086b Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Fri, 12 Nov 2021 08:51:43 +0100 Subject: [PATCH 56/94] MQTT event adapter --- ...ecutorRemovedEventListenerHttpAdapter.java | 6 ++- .../mqtt/ExecutorRemovedEventListener | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java index fcf9b52..58bbb95 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java @@ -18,7 +18,8 @@ import org.springframework.web.bind.annotation.RestController; public class ExecutorRemovedEventListenerHttpAdapter { // TODO: add annotations for request method, request URI, etc. - public void handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) { + @PostMapping(path = "/executors/{taskType}/{executorId}") + public ResponseEntity handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) { // TODO: implement logic ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( @@ -27,6 +28,7 @@ public class ExecutorRemovedEventListenerHttpAdapter { ExecutorRemovedHandler newExecutorHandler = new ExecutorRemovedHandler(); newExecutorHandler.handleExecutorRemovedEvent(executorRemovedEvent); - + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener new file mode 100644 index 0000000..087479c --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener @@ -0,0 +1,46 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import ch.unisg.tapas.auctionhouse.application.handler.ExecutorRemovedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + * Listener that handles events when an executor was removed to this TAPAS application. + * + * This class is only provided as an example to help you bootstrap the project. + */ +public class ExecutorRemovedEventListenerMqttAdapter extends AuctionEventMqttListener { + private static final Logger LOGGER = LogManager.getLogger(ExecutorRemovedEventListenerMqttAdapter.class); + + @Override + public boolean handleEvent(MqttMessage message) { + String payload = new String(message.getPayload()); + + try { + // Note: this messge representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String executorId = data.get("executorId").asText(); + + ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( + new ExecutorRegistry.ExecutorIdentifier(executorId) + ); + + ExecutorRemovedHandler newExecutorHandler = new ExecutorRemovedHandler(); + newExecutorHandler.handleNewExecutorEvent(executorRemovedEvent); + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + + return true; + } +} -- 2.45.1 From c48a402e559a39182292d07c04eb8c6c142b8a34 Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Fri, 12 Nov 2021 08:59:09 +0100 Subject: [PATCH 57/94] 2.0 --- ...ntListener => ExecutorRemovedEventListenerMqttAdapter.java} | 0 .../application/handler/ExecutorRemovedHandler.java | 3 +++ 2 files changed, 3 insertions(+) rename tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/{ExecutorRemovedEventListener => ExecutorRemovedEventListenerMqttAdapter.java} (100%) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java similarity index 100% rename from tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListener rename to tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java index c3bfed8..aa47d64 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java @@ -16,4 +16,7 @@ public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler { public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) { return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorId()); } + + public void handleNewExecutorEvent(ExecutorRemovedEvent executorRemovedEvent) { + } } -- 2.45.1 From 343d33270a9b316667e54213fe1f4c4db8d6cf70 Mon Sep 17 00:00:00 2001 From: ronsei Date: Fri, 12 Nov 2021 10:21:05 +0100 Subject: [PATCH 58/94] Extending the README.md for tapas-tasks with details on setting up MongoDB as DB for repositories. --- tapas-tasks/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tapas-tasks/README.md b/tapas-tasks/README.md index 90016c3..55f120f 100644 --- a/tapas-tasks/README.md +++ b/tapas-tasks/README.md @@ -167,3 +167,25 @@ Date: Sun, 17 Oct 2021 21:32:25 GMT Internally, this request is mapped to a [TaskExecutedEvent](src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java). The HTTP response returns a `200 OK` status code together with the updated representation of the task. + +## Working with MongoDB +The provided TAPAS Tasks service is connected to a MongoDB as a repository for persisting data. + +Here are some pointers to start integrating the MongoDB with the other microservices: +* [application.properties](src/main/resources/application.properties) defines the + * URI of the DB server that Spring will connect to (`mongodb`service running in Docker container). Username and password for the server can be found in [docker-compose.yml](../docker-compose.yml). + * Name of the database for the microservice (`tapas-tasks`) +* [docker-compose.yml](../docker-compose.yml) defines + * in lines 74-82: the configuration of the mongodb service based on the mongodb container including the root username and password (once deployed this cannot be changed anymore!) + * in lines 84-102: the configuration of a web application called `mongo-express` to manage the MongoDB server. The web app can be reached via the URI: [http://dbadmin.${PUB_IP}.nip.io]([http://dbadmin.${PUB_IP}.nip.io]). Login credentials for mongo-express can be found in lines 89 and 90. + * in lines 104-105: the volume to be used by the mongodb service for writing and storing data (do not forget!). +* The [pom.xml](./pom.xml) needs to have `spring-boot-starter-data-mongodb` and `spring-data-mongodb` as new dependencies. +* The [TapasTasksApplication.java](/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java) specifies in line 9 the location of the MongoRepository classes for the microservice. +* The [persistence.mongodb](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb) package has the relevant classes to work with MongoDB: + * The [MongoTaskDocument.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java) class defines the attributes of a Document for storing a task in the collection `tasks`. + * The [TaskRepository.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java) class specifies the MongoRepository. + * The [TaskPersistenceAdapter.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java) implements the two ports to add a new task ([AddTaskPort](/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java)) and retrieve a task ([LoadTaskPort](/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java)). These ports are used in the classes [AddNewTaskToTaskListService.java](/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java) and [RetrieveTaskFromTaskListService.java](/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java). + +#### General hints: +* To not overload the VMs we recommend to use only one MongoDB server that all microservices connect to. Per microservice you could use one database or one collection (discuss in your ADRs!). To use more than one MongoDB server you have to extend the [docker-compose.yml](../docker-compose.yml) file by basically replicating lines 74-105 and changing the names of the services and volumes to be unique (ask your tutors!). +* For local testing you have to install the MongoDB server locally on your computers and change the `spring.data.mongodb.uri` String in [application.properties](./src/main/resources/application.properties). -- 2.45.1 From 2f42da485d68f50e102e9329f3ac4a56a15de113 Mon Sep 17 00:00:00 2001 From: reynisson Date: Fri, 12 Nov 2021 13:30:16 +0100 Subject: [PATCH 59/94] Fixed imports so that the class builds --- .../application/port/in/LaunchAuctionCommand.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java index 37eb5db..626fa49 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java @@ -1,11 +1,10 @@ package ch.unisg.tapas.auctionhouse.application.port.in; import ch.unisg.tapas.auctionhouse.domain.Auction; -import ch.unisg.common.SelfValidating; -import lombok.NonNull; +import ch.unisg.tapas.common.SelfValidating; import lombok.Value; -import javax.validation.constraint.NotNull; +import javax.validation.constraints.NotNull; /** * Command for launching an auction in this auction house. -- 2.45.1 From 55c094fc56db6abc9c641fd45448af1e8bef342b Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 14:28:45 +0100 Subject: [PATCH 60/94] Implemented the new executor added event over mqtt --- executor-pool/pom.xml | 6 +++ .../ch/unisg/common/ConfigProperties.java | 23 ++++++++++ .../ch/unisg/executorpool/TestController.java | 12 ------ .../common/clients/TapasMqttClient.java | 41 ++++++++++++++++++ ...ewExecutorToExecutorPoolWebController.java | 8 +++- .../PublishExecutorAddedEventAdapter.java | 43 +++++++++++++++++++ .../port/out/ExecutorAddedEventPort.java | 8 ++++ .../AddNewExecutorToExecutorPoolService.java | 17 ++++++-- .../domain/ExecutorAddedEvent.java | 10 +++++ .../src/main/resources/application.properties | 2 + 10 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java delete mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/TestController.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/clients/TapasMqttClient.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorAddedEventAdapter.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorAddedEventPort.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorAddedEvent.java diff --git a/executor-pool/pom.xml b/executor-pool/pom.xml index 2e75dcc..512235d 100644 --- a/executor-pool/pom.xml +++ b/executor-pool/pom.xml @@ -63,6 +63,12 @@ javax.transaction-api compile + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + compile + diff --git a/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java b/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java new file mode 100644 index 0000000..b46bf63 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java @@ -0,0 +1,23 @@ +package ch.unisg.common; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.net.URI; + +@Component +public class ConfigProperties { + @Autowired + private Environment environment; + + /** + * Retrieves the URI of the WebSub hub. In this project, we use a single WebSub hub, but we could + * use multiple. + * + * @return the URI of the WebSub hub + */ + public URI getMqttBrokerUri() { + return URI.create(environment.getProperty("mqtt.broker.uri")); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java b/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java deleted file mode 100644 index ca29e09..0000000 --- a/executor-pool/src/main/java/ch/unisg/executorpool/TestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unisg.executorpool; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TestController { - @RequestMapping("/") - public String index() { - return "Hello World! Executor Pool"; - } -} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/clients/TapasMqttClient.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/clients/TapasMqttClient.java new file mode 100644 index 0000000..0b24b81 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/clients/TapasMqttClient.java @@ -0,0 +1,41 @@ +package ch.unisg.executorpool.adapter.common.clients; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class TapasMqttClient { + private static final Logger LOGGER = LogManager.getLogger(TapasMqttClient.class); + + private static TapasMqttClient tapasClient = null; + + private MqttClient mqttClient; + private final String mqttClientId; + private final String brokerAddress; + + private TapasMqttClient(String brokerAddress) { + this.mqttClientId = UUID.randomUUID().toString(); + this.brokerAddress = brokerAddress; + } + + public static synchronized TapasMqttClient getInstance(String brokerAddress) { + + if (tapasClient == null) { + tapasClient = new TapasMqttClient(brokerAddress); + } + + return tapasClient; + } + + public void publishMessage(String topic, String payload) throws MqttException { + mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence()); + mqttClient.connect(); + MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); + mqttClient.publish(topic, message); + mqttClient.disconnect(); + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java index 5a2dc09..ff464d3 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java @@ -1,5 +1,6 @@ package ch.unisg.executorpool.adapter.in.web; +import ch.unisg.executorpool.adapter.common.clients.TapasMqttClient; import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolUseCase; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolCommand; @@ -13,6 +14,10 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import javax.validation.ConstraintViolationException; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import org.eclipse.paho.client.mqttv3.*; @RestController public class AddNewExecutorToExecutorPoolWebController { @@ -24,7 +29,7 @@ public class AddNewExecutorToExecutorPoolWebController { @PostMapping(path = "/executor-pool/AddExecutor", consumes = {ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE}) public ResponseEntity addNewExecutorToExecutorPool(@RequestBody ExecutorJsonRepresentation payload){ - try{ + try { AddNewExecutorToExecutorPoolCommand command = new AddNewExecutorToExecutorPoolCommand( new ExecutorClass.ExecutorUri(URI.create(payload.getExecutorUri())), new ExecutorClass.ExecutorTaskType(payload.getExecutorTaskType()) @@ -36,6 +41,7 @@ public class AddNewExecutorToExecutorPoolWebController { responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE); return new ResponseEntity<>(ExecutorJsonRepresentation.serialize(newExecutor), responseHeaders, HttpStatus.CREATED); + } catch (ConstraintViolationException e){ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorAddedEventAdapter.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorAddedEventAdapter.java new file mode 100644 index 0000000..323bcbb --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorAddedEventAdapter.java @@ -0,0 +1,43 @@ +package ch.unisg.executorpool.adapter.out.messaging; + +import ch.unisg.common.ConfigProperties; +import ch.unisg.executorpool.adapter.common.clients.TapasMqttClient; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; +import ch.unisg.executorpool.application.port.out.ExecutorAddedEventPort; +import ch.unisg.executorpool.domain.ExecutorAddedEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.net.URI; + +@Component +@Primary +public class PublishExecutorAddedEventAdapter implements ExecutorAddedEventPort { + + private static final Logger LOGGER = LogManager.getLogger(PublishExecutorAddedEventAdapter.class); + + // TODO Can't autowire. Find fix + /* + @Autowired + private ConfigProperties config; + */ + + @Autowired + private Environment environment; + + @Override + public void publishExecutorAddedEvent(ExecutorAddedEvent event){ + try{ + var mqttClient = TapasMqttClient.getInstance(environment.getProperty("mqtt.broker.uri")); + mqttClient.publishMessage("ch/unisg/tapas/executors/added", ExecutorJsonRepresentation.serialize(event.getExecutorClass())); + } + catch (MqttException e){ + LOGGER.error(e.getMessage(), e); + } + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorAddedEventPort.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorAddedEventPort.java new file mode 100644 index 0000000..ad75c75 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorAddedEventPort.java @@ -0,0 +1,8 @@ +package ch.unisg.executorpool.application.port.out; + +import ch.unisg.executorpool.domain.ExecutorAddedEvent; +import org.eclipse.paho.client.mqttv3.MqttException; + +public interface ExecutorAddedEventPort { + void publishExecutorAddedEvent(ExecutorAddedEvent event); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java index 200739b..393024a 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java @@ -2,24 +2,33 @@ package ch.unisg.executorpool.application.service; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolUseCase; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolCommand; +import ch.unisg.executorpool.application.port.out.ExecutorAddedEventPort; +import ch.unisg.executorpool.domain.ExecutorAddedEvent; import ch.unisg.executorpool.domain.ExecutorClass; import ch.unisg.executorpool.domain.ExecutorPool; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.yaml.snakeyaml.constructor.DuplicateKeyException; import javax.transaction.Transactional; -import javax.validation.ConstraintViolationException; -@RequiredArgsConstructor @Component @Transactional public class AddNewExecutorToExecutorPoolService implements AddNewExecutorToExecutorPoolUseCase { + private final ExecutorAddedEventPort executorAddedEventPort; + + public AddNewExecutorToExecutorPoolService(ExecutorAddedEventPort executorAddedEventPort){ + this.executorAddedEventPort = executorAddedEventPort; + } + @Override public ExecutorClass addNewExecutorToExecutorPool(AddNewExecutorToExecutorPoolCommand command){ ExecutorPool executorPool = ExecutorPool.getExecutorPool(); + var newExecutor = executorPool.addNewExecutor(command.getExecutorUri(), command.getExecutorTaskType()); - return executorPool.addNewExecutor(command.getExecutorUri(), command.getExecutorTaskType()); + var executorAddedEvent = new ExecutorAddedEvent(newExecutor); + executorAddedEventPort.publishExecutorAddedEvent(executorAddedEvent); + + return newExecutor; } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorAddedEvent.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorAddedEvent.java new file mode 100644 index 0000000..6ec291e --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorAddedEvent.java @@ -0,0 +1,10 @@ +package ch.unisg.executorpool.domain; + +import lombok.Getter; + +public class ExecutorAddedEvent { + @Getter + private ExecutorClass executorClass; + + public ExecutorAddedEvent(ExecutorClass executorClass) { this.executorClass = executorClass; } +} diff --git a/executor-pool/src/main/resources/application.properties b/executor-pool/src/main/resources/application.properties index 8f91ca7..0c9ba7e 100644 --- a/executor-pool/src/main/resources/application.properties +++ b/executor-pool/src/main/resources/application.properties @@ -1 +1,3 @@ server.port=8083 + +mqtt.broker.uri=tcp://localhost:1883 -- 2.45.1 From 2999fb294c32f334943c127fd764201361468260 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 15:16:42 +0100 Subject: [PATCH 61/94] Implemented the auction started event over mqtt --- .../ch/unisg/common/ConfigProperties.java | 5 ++- .../tapas/TapasAuctionHouseApplication.java | 4 +-- .../common/clients/TapasMqttClient.java | 5 ++- .../mqtt/AuctionEventsMqttDispatcher.java | 2 +- ...PublishAuctionStartedEventMqttAdapter.java | 36 +++++++++++++++++++ ...blishAuctionStartedEventWebSubAdapter.java | 1 - .../unisg/tapas/common/ConfigProperties.java | 10 ++++++ .../src/main/resources/application.properties | 3 ++ 8 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java diff --git a/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java b/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java index b46bf63..253922c 100644 --- a/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java +++ b/executor-pool/src/main/java/ch/unisg/common/ConfigProperties.java @@ -12,10 +12,9 @@ public class ConfigProperties { private Environment environment; /** - * Retrieves the URI of the WebSub hub. In this project, we use a single WebSub hub, but we could - * use multiple. + * Retrieves the URI of the MQTT broker. * - * @return the URI of the WebSub hub + * @return the URI of the MQTT broker */ public URI getMqttBrokerUri() { return URI.create(environment.getProperty("mqtt.broker.uri")); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 8fc22d0..db57cc7 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -21,14 +21,14 @@ public class TapasAuctionHouseApplication { private static final Logger LOGGER = LogManager.getLogger(TapasAuctionHouseApplication.class); public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; - public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + public static String MQTT_BROKER = "tcp://localhost:1883"; public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); // We will use these bootstrap methods in Week 6: // bootstrapMarketplaceWithWebSub(); - // bootstrapMarketplaceWithMqtt(); + bootstrapMarketplaceWithMqtt(); tapasAuctioneerApp.run(args); } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java index 708d512..db5903c 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java @@ -68,7 +68,10 @@ public class TapasMqttClient { mqttClient.subscribe(topic); } - private void publishMessage(String topic, String payload) throws MqttException { + public void publishMessage(String topic, String payload) throws MqttException { + mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence()); + mqttClient.connect(); + MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); mqttClient.publish(topic, message); } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java index e5eaf12..3e55d5e 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java @@ -26,7 +26,7 @@ public class AuctionEventsMqttDispatcher { // TODO: Register here your topics and event listener adapters private void initRouter() { - router.put("ch/unisg/tapas-group-tutors/executors", new ExecutorAddedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas/executors/added", new ExecutorAddedEventListenerMqttAdapter()); } /** diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java new file mode 100644 index 0000000..d5bb0fc --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java @@ -0,0 +1,36 @@ +package ch.unisg.tapas.auctionhouse.adapter.out.messaging.websub; + +import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; +import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort; +import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent; +import ch.unisg.tapas.common.ConfigProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Component +@Primary +public class PublishAuctionStartedEventMqttAdapter implements AuctionStartedEventPort { + + private static final Logger LOGGER = LogManager.getLogger(PublishAuctionStartedEventMqttAdapter.class); + + @Autowired + private ConfigProperties config; + + @Override + public void publishAuctionStartedEvent(AuctionStartedEvent event) { + try{ + var mqttClient = TapasMqttClient.getInstance(config.getMqttBrokerUri().toString(), new AuctionEventsMqttDispatcher()); + mqttClient.publishMessage("ch/unisg/tapas/auctions", AuctionJsonRepresentation.serialize(event.getAuction())); + } + catch (MqttException | JsonProcessingException e){ + LOGGER.error(e.getMessage(), e); + } + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java index 9e6ec67..73451e4 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java @@ -23,7 +23,6 @@ import java.util.stream.Collectors; * This class is a template for publishing auction started events via WebSub. */ @Component -@Primary public class PublishAuctionStartedEventWebSubAdapter implements AuctionStartedEventPort { // You can use this object to retrieve properties from application.properties, e.g. the // WebSub hub publish endpoint, etc. diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java index 748afda..2933465 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/common/ConfigProperties.java @@ -61,4 +61,14 @@ public class ConfigProperties { public URI getTaskListUri() { return URI.create(environment.getProperty("tasks.list.uri")); } + + + /** + * Retrieves the URI of the MQTT broker. + * + * @return the URI of the MQTT broker + */ + public URI getMqttBrokerUri() { + return URI.create(environment.getProperty("mqtt.broker.uri")); + } } diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index e9c609f..1ededee 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -6,3 +6,6 @@ websub.hub.publish=https://websub.appspot.com/ group=tapas-group-tutors auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ tasks.list.uri=https://tapas-tasks.86-119-34-23.nip.io/ + + +mqtt.broker.uri=tcp://localhost:1883 -- 2.45.1 From 41b0e25a5e985bef5a174d0484ff603535ce1047 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 16:06:03 +0100 Subject: [PATCH 62/94] Adapted the auction house to receive and handle the new executor event over mqtt --- .../tapas/TapasAuctionHouseApplication.java | 5 +++ .../common/clients/TapasMqttClient.java | 2 +- ...ExecutorAddedEventListenerHttpAdapter.java | 34 ------------------- ...ecutorRemovedEventListenerHttpAdapter.java | 16 --------- ...ExecutorAddedEventListenerMqttAdapter.java | 12 ++++--- .../handler/ExecutorAddedHandler.java | 2 +- .../handler/ExecutorRemovedHandler.java | 2 +- .../port/in/ExecutorAddedEvent.java | 11 +++--- .../port/in/ExecutorRemovedEvent.java | 11 +++--- .../auctionhouse/domain/ExecutorRegistry.java | 23 +++++++------ 10 files changed, 39 insertions(+), 79 deletions(-) delete mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java delete mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index db57cc7..46dafb7 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -4,9 +4,11 @@ import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient; import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; import ch.unisg.tapas.auctionhouse.adapter.common.clients.WebSubSubscriber; import ch.unisg.tapas.common.AuctionHouseResourceDirectory; +import ch.unisg.tapas.common.ConfigProperties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -20,6 +22,9 @@ import java.util.List; public class TapasAuctionHouseApplication { private static final Logger LOGGER = LogManager.getLogger(TapasAuctionHouseApplication.class); + @Autowired + private ConfigProperties config; + public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; public static String MQTT_BROKER = "tcp://localhost:1883"; diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java index db5903c..1a30bc4 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/TapasMqttClient.java @@ -71,7 +71,7 @@ public class TapasMqttClient { public void publishMessage(String topic, String payload) throws MqttException { mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence()); mqttClient.connect(); - + MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); mqttClient.publish(topic, message); } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java deleted file mode 100644 index 3511b7d..0000000 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorAddedEventListenerHttpAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http; - -import ch.unisg.tapas.auctionhouse.application.handler.ExecutorAddedHandler; -import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent; -import ch.unisg.tapas.auctionhouse.domain.Auction; -import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Template for receiving an executor added event via HTTP - */ -@RestController -public class ExecutorAddedEventListenerHttpAdapter { - - @PostMapping(path = "/executors/{taskType}/{executorId}") - public ResponseEntity handleExecutorAddedEvent(@PathVariable("taskType") String taskType, - @PathVariable("executorId") String executorId) { - - ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( - new ExecutorRegistry.ExecutorIdentifier(executorId), - new Auction.AuctionedTaskType(taskType) - ); - - ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler(); - newExecutorHandler.handleNewExecutorEvent(executorAddedEvent); - - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } -} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java deleted file mode 100644 index 53811f9..0000000 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java +++ /dev/null @@ -1,16 +0,0 @@ -package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http; - -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; - -/** - * Template for handling an executor removed event received via an HTTP request - */ -@RestController -public class ExecutorRemovedEventListenerHttpAdapter { - - // TODO: add annotations for request method, request URI, etc. - public void handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) { - // TODO: implement logic - } -} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java index 2f661d1..dd2d120 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java @@ -11,6 +11,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttMessage; +import java.net.URI; + /** * Listener that handles events when an executor was added to this TAPAS application. * @@ -24,16 +26,16 @@ public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListe String payload = new String(message.getPayload()); try { - // Note: this messge representation is provided only as an example. You should use a + // Note: this message representation is provided only as an example. You should use a // representation that makes sense in the context of your application. JsonNode data = new ObjectMapper().readTree(payload); - String taskType = data.get("taskType").asText(); - String executorId = data.get("executorId").asText(); + String executorUri = data.get("executorUri").asText(); + String executorTaskType = data.get("executorTaskType").asText(); ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( - new ExecutorRegistry.ExecutorIdentifier(executorId), - new Auction.AuctionedTaskType(taskType) + new ExecutorRegistry.ExecutorUri(URI.create(executorUri)), + new Auction.AuctionedTaskType(executorTaskType) ); ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler(); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java index 624e669..fc30e11 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorAddedHandler.java @@ -11,6 +11,6 @@ public class ExecutorAddedHandler implements ExecutorAddedEventHandler { @Override public boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent) { return ExecutorRegistry.getInstance().addExecutor(executorAddedEvent.getTaskType(), - executorAddedEvent.getExecutorId()); + executorAddedEvent.getExecutorUri()); } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java index c3bfed8..9a68da1 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java @@ -14,6 +14,6 @@ public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler { @Override public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) { - return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorId()); + return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorUri()); } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java index 5a53b94..7d647e1 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorAddedEvent.java @@ -1,7 +1,8 @@ package ch.unisg.tapas.auctionhouse.application.port.in; import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskType; -import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorUri; import ch.unisg.tapas.common.SelfValidating; import lombok.Value; @@ -13,7 +14,7 @@ import javax.validation.constraints.NotNull; @Value public class ExecutorAddedEvent extends SelfValidating { @NotNull - private final ExecutorIdentifier executorId; + private final ExecutorRegistry.ExecutorUri executorUri; @NotNull private final AuctionedTaskType taskType; @@ -21,10 +22,10 @@ public class ExecutorAddedEvent extends SelfValidating { /** * Constructs an executor added event. * - * @param executorId the identifier of the executor that was added to this TAPAS application + * @param executorUri the identifier of the executor that was added to this TAPAS application */ - public ExecutorAddedEvent(ExecutorIdentifier executorId, AuctionedTaskType taskType) { - this.executorId = executorId; + public ExecutorAddedEvent(ExecutorUri executorUri, AuctionedTaskType taskType) { + this.executorUri = executorUri; this.taskType = taskType; this.validateSelf(); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java index 4d5c910..a1633fe 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/ExecutorRemovedEvent.java @@ -1,6 +1,7 @@ package ch.unisg.tapas.auctionhouse.application.port.in; -import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorUri; import ch.unisg.tapas.common.SelfValidating; import lombok.Value; @@ -12,15 +13,15 @@ import javax.validation.constraints.NotNull; @Value public class ExecutorRemovedEvent extends SelfValidating { @NotNull - private final ExecutorIdentifier executorId; + private final ExecutorUri executorUri; /** * Constructs an executor removed event. * - * @param executorId the identifier of the executor that was removed from this TAPAS application + * @param executorUri */ - public ExecutorRemovedEvent(ExecutorIdentifier executorId) { - this.executorId = executorId; + public ExecutorRemovedEvent(ExecutorUri executorUri) { + this.executorUri = executorUri; this.validateSelf(); } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java index 9da3756..1aedc80 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/ExecutorRegistry.java @@ -2,6 +2,7 @@ package ch.unisg.tapas.auctionhouse.domain; import lombok.Value; +import java.net.URI; import java.util.*; /** @@ -13,7 +14,7 @@ import java.util.*; public class ExecutorRegistry { private static ExecutorRegistry registry; - private final Map> executors; + private final Map> executors; private ExecutorRegistry() { this.executors = new Hashtable<>(); @@ -31,14 +32,14 @@ public class ExecutorRegistry { * Adds an executor to the registry for a given task type. * * @param taskType the type of the task - * @param executorIdentifier the identifier of the executor (can be any string) + * @param executorUri the executor's URI * @return true unless a runtime exception occurs */ - public boolean addExecutor(Auction.AuctionedTaskType taskType, ExecutorIdentifier executorIdentifier) { - Set taskTypeExecs = executors.getOrDefault(taskType, + public boolean addExecutor(Auction.AuctionedTaskType taskType, ExecutorUri executorUri) { + Set taskTypeExecs = executors.getOrDefault(taskType, Collections.synchronizedSet(new HashSet<>())); - taskTypeExecs.add(executorIdentifier); + taskTypeExecs.add(executorUri); executors.put(taskType, taskTypeExecs); return true; @@ -47,17 +48,17 @@ public class ExecutorRegistry { /** * Removes an executor from the registry. The executor is disassociated from all known task types. * - * @param executorIdentifier the identifier of the executor (can be any string) + * @param executorUri the executor's URI * @return true unless a runtime exception occurs */ - public boolean removeExecutor(ExecutorIdentifier executorIdentifier) { + public boolean removeExecutor(ExecutorUri executorUri) { Iterator iterator = executors.keySet().iterator(); while (iterator.hasNext()) { Auction.AuctionedTaskType taskType = iterator.next(); - Set set = executors.get(taskType); + Set set = executors.get(taskType); - set.remove(executorIdentifier); + set.remove(executorUri); if (set.isEmpty()) { iterator.remove(); @@ -80,7 +81,7 @@ public class ExecutorRegistry { // Value Object for the executor identifier @Value - public static class ExecutorIdentifier { - String value; + public static class ExecutorUri { + URI value; } } -- 2.45.1 From 396f24e0076875933bdab96fa4c34fd7b055f552 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 16:34:45 +0100 Subject: [PATCH 63/94] Implemented the executor removed event over mqtt --- .../PublishExecutorRemovedEventAdapter.java | 41 +++++++++++++++++++ .../port/out/ExecutorRemovedEventPort.java | 7 ++++ ...RemoveExecutorFromExecutorPoolService.java | 19 ++++++++- .../domain/ExecutorRemovedEvent.java | 11 +++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorRemovedEventAdapter.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorRemovedEventPort.java create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorRemovedEvent.java diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorRemovedEventAdapter.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorRemovedEventAdapter.java new file mode 100644 index 0000000..aa01165 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/out/messaging/PublishExecutorRemovedEventAdapter.java @@ -0,0 +1,41 @@ +package ch.unisg.executorpool.adapter.out.messaging; + +import ch.unisg.executorpool.adapter.common.clients.TapasMqttClient; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; +import ch.unisg.executorpool.application.port.out.ExecutorRemovedEventPort; +import ch.unisg.executorpool.domain.ExecutorAddedEvent; +import ch.unisg.executorpool.domain.ExecutorRemovedEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +@Primary +public class PublishExecutorRemovedEventAdapter implements ExecutorRemovedEventPort { + + private static final Logger LOGGER = LogManager.getLogger(PublishExecutorAddedEventAdapter.class); + + // TODO Can't autowire. Find fix + /* + @Autowired + private ConfigProperties config; + */ + + @Autowired + private Environment environment; + + @Override + public void publishExecutorRemovedEvent(ExecutorRemovedEvent event){ + try{ + var mqttClient = TapasMqttClient.getInstance(environment.getProperty("mqtt.broker.uri")); + mqttClient.publishMessage("ch/unisg/tapas/executors/removed", ExecutorJsonRepresentation.serialize(event.getExecutorClass())); + } + catch (MqttException e){ + LOGGER.error(e.getMessage(), e); + } + } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorRemovedEventPort.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorRemovedEventPort.java new file mode 100644 index 0000000..b905858 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/out/ExecutorRemovedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executorpool.application.port.out; + +import ch.unisg.executorpool.domain.ExecutorRemovedEvent; + +public interface ExecutorRemovedEventPort { + void publishExecutorRemovedEvent(ExecutorRemovedEvent event); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java index a606f57..4d2457d 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java @@ -2,21 +2,36 @@ package ch.unisg.executorpool.application.service; import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolCommand; import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolUseCase; +import ch.unisg.executorpool.application.port.out.ExecutorRemovedEventPort; import ch.unisg.executorpool.domain.ExecutorClass; import ch.unisg.executorpool.domain.ExecutorPool; +import ch.unisg.executorpool.domain.ExecutorRemovedEvent; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import javax.transaction.Transactional; import java.util.Optional; -@RequiredArgsConstructor @Component @Transactional public class RemoveExecutorFromExecutorPoolService implements RemoveExecutorFromExecutorPoolUseCase { + + private final ExecutorRemovedEventPort executorRemovedEventPort; + + public RemoveExecutorFromExecutorPoolService(ExecutorRemovedEventPort executorRemovedEventPort){ + this.executorRemovedEventPort = executorRemovedEventPort; + } + @Override public Optional removeExecutorFromExecutorPool(RemoveExecutorFromExecutorPoolCommand command){ ExecutorPool executorPool = ExecutorPool.getExecutorPool(); - return executorPool.removeExecutorByIpAndPort(command.getExecutorUri()); + var removedExecutor = executorPool.removeExecutorByIpAndPort(command.getExecutorUri()); + + if(removedExecutor.isPresent()){ + var executorRemovedEvent = new ExecutorRemovedEvent(removedExecutor.get()); + executorRemovedEventPort.publishExecutorRemovedEvent(executorRemovedEvent); + } + + return removedExecutor; } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorRemovedEvent.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorRemovedEvent.java new file mode 100644 index 0000000..a038928 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorRemovedEvent.java @@ -0,0 +1,11 @@ +package ch.unisg.executorpool.domain; + +import ch.unisg.executorpool.domain.ExecutorClass; +import lombok.Getter; + +public class ExecutorRemovedEvent { + @Getter + private ExecutorClass executorClass; + + public ExecutorRemovedEvent(ExecutorClass executorClass) { this.executorClass = executorClass; } +} -- 2.45.1 From b37141f5cefcdcfef0c4b17778670f090b5e259e Mon Sep 17 00:00:00 2001 From: "julius.lautz" Date: Sun, 14 Nov 2021 16:51:42 +0100 Subject: [PATCH 64/94] fixed issues --- .../adapter/common/formats/AuctionJsonRepresentation.java | 6 ++++-- .../application/service/StartAuctionService.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java index 4500423..ea4cf2c 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter; +import java.sql.Timestamp; + /** * Used to expose a representation of the state of an auction through an interface. This class is * only meant as a starting point when defining a uniform HTTP API for the Auction House: feel free @@ -28,12 +30,12 @@ public class AuctionJsonRepresentation { private String taskType; @Getter @Setter - private Integer deadline; + private Timestamp deadline; public AuctionJsonRepresentation() { } public AuctionJsonRepresentation(String auctionId, String auctionHouseUri, String taskUri, - String taskType, Integer deadline) { + String taskType, Timestamp deadline) { this.auctionId = auctionId; this.auctionHouseUri = auctionHouseUri; this.taskUri = taskUri; diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java index b9d9d3d..60c5f24 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.sql.Timestamp; import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -26,7 +27,7 @@ import java.util.concurrent.TimeUnit; public class StartAuctionService implements LaunchAuctionUseCase { private static final Logger LOGGER = LogManager.getLogger(StartAuctionService.class); - private final static int DEFAULT_AUCTION_DEADLINE_MILLIS = 10000; + private final Timestamp DEFAULT_AUCTION_DEADLINE_MILLIS = Timestamp.valueOf("1970-01-01 00:00:01"); // Event port used to publish an auction started event private final AuctionStartedEventPort auctionStartedEventPort; -- 2.45.1 From 1b7395ba0d04f244b530978bd27edd558ff47e5a Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 19:26:19 +0100 Subject: [PATCH 65/94] Implemented the handling of the mqtt executor removed event --- .../in/messaging/mqtt/AuctionEventsMqttDispatcher.java | 1 + .../mqtt/ExecutorRemovedEventListenerMqttAdapter.java | 8 +++++--- .../application/handler/ExecutorRemovedHandler.java | 3 --- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java index 3e55d5e..7d30453 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java @@ -27,6 +27,7 @@ public class AuctionEventsMqttDispatcher { // TODO: Register here your topics and event listener adapters private void initRouter() { router.put("ch/unisg/tapas/executors/added", new ExecutorAddedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas/executors/removed", new ExecutorRemovedEventListenerMqttAdapter()); } /** diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java index 087479c..4f4db7a 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java @@ -11,6 +11,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttMessage; +import java.net.URI; + /** * Listener that handles events when an executor was removed to this TAPAS application. * @@ -28,14 +30,14 @@ public class ExecutorRemovedEventListenerMqttAdapter extends AuctionEventMqttLis // representation that makes sense in the context of your application. JsonNode data = new ObjectMapper().readTree(payload); - String executorId = data.get("executorId").asText(); + String executorUri = data.get("executorUri").asText(); ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( - new ExecutorRegistry.ExecutorIdentifier(executorId) + new ExecutorRegistry.ExecutorUri(URI.create(executorUri)) ); ExecutorRemovedHandler newExecutorHandler = new ExecutorRemovedHandler(); - newExecutorHandler.handleNewExecutorEvent(executorRemovedEvent); + newExecutorHandler.handleExecutorRemovedEvent(executorRemovedEvent); } catch (JsonProcessingException | NullPointerException e) { LOGGER.error(e.getMessage(), e); return false; diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java index f63950d..9a68da1 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/ExecutorRemovedHandler.java @@ -16,7 +16,4 @@ public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler { public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) { return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorUri()); } - - public void handleNewExecutorEvent(ExecutorRemovedEvent executorRemovedEvent) { - } } -- 2.45.1 From 333f6aab2117f652f5404d25371446269ce4a38c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Nov 2021 20:46:42 +0100 Subject: [PATCH 66/94] finished websub implementation --- .../tapas/TapasAuctionHouseApplication.java | 12 ++------ .../common/clients/WebSubSubscriber.java | 4 +-- ...tionStartedEventListenerWebSubAdapter.java | 30 ++++++++++++++----- .../websub/ValidateIntentWebSubAdapter.java | 3 -- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 3459cff..1f958d9 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -27,23 +27,17 @@ public class TapasAuctionHouseApplication { public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); - + // We will use these bootstrap methods in Week 6: + bootstrapMarketplaceWithWebSub(); + // bootstrapMarketplaceWithMqtt(); tapasAuctioneerApp.run(args); - - // We will use these bootstrap methods in Week 6: - - // bootstrapMarketplaceWithMqtt(); - bootstrapMarketplaceWithWebSub(); } - /** * Discovers auction houses and subscribes to WebSub notifications */ private static void bootstrapMarketplaceWithWebSub() { - System.out.println("HAHA"); List auctionHouseEndpoints = discoverAuctionHouseEndpoints(); - LOGGER.info("Found auction house endpoints: " + auctionHouseEndpoints); WebSubSubscriber subscriber = new WebSubSubscriber(); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java index 8066010..5b3fc32 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java @@ -9,10 +9,7 @@ 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.http.HttpStatus; -import org.springframework.stereotype.Component; /** * Subscribes to the WebSub hubs of auction houses discovered at run time. This class is instantiated @@ -38,6 +35,7 @@ public class WebSubSubscriber { subscribeToWebSub(topic); + // Shoudl be done :D // TODO Subscribe to the auction house endpoint via WebSub: // 1. Send a request to the auction house in order to discover the WebSub hub to subscribe to. // The request URI should depend on the design of the Auction House HTTP API. diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java index 9e7a356..4f67dad 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java @@ -1,6 +1,18 @@ package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionDeadline; +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionHouseUri; +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionId; +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskType; +import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskUri; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; import org.json.JSONArray; import org.springframework.http.HttpStatus; @@ -20,16 +32,20 @@ public class AuctionStartedEventListenerWebSubAdapter { /** * Controller which listens to auction-started callbacks * @return 200 OK + * @throws URISyntaxException **/ @PostMapping(path = "/auction-started") - public ResponseEntity handleExecutorAddedEvent(@RequestBody String payload) { + public ResponseEntity handleExecutorAddedEvent(@RequestBody Collection payload) throws URISyntaxException { - // Payload should be a JSONArray with auctions - JSONArray jsonArray = new JSONArray(payload); - for (Object auction : jsonArray) { - System.out.println(auction); - // TODO logic to call handleAuctionStartedEvent() - // auctionStartedHandler.handleAuctionStartedEvent(auctionStartedEvent) + for (AuctionJsonRepresentation auction : payload) { + auctionStartedHandler.handleAuctionStartedEvent( + new AuctionStartedEvent( + new Auction(new AuctionId(auction.getAuctionId()), + new AuctionHouseUri(new URI(auction.getAuctionHouseUri())), + new AuctionedTaskUri(new URI(auction.getTaskUri())), + new AuctionedTaskType(auction.getTaskType()), + new AuctionDeadline(auction.getDeadline())) + )); } return new ResponseEntity<>(HttpStatus.OK); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java index 9e25a69..8509b09 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java @@ -18,9 +18,6 @@ public class ValidateIntentWebSubAdapter { @GetMapping(path = "/auction-started") public ResponseEntity handleExecutorAddedEvent(@RequestParam("hub.challenge") String challenge) { - - - // Different implementation depending on local development or production if (environment.equalsIgnoreCase("development")) { HttpHeaders headers = new HttpHeaders(); -- 2.45.1 From 613c1482d7365408e0512e0d3f2ff39a4a481dad Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Nov 2021 22:09:10 +0100 Subject: [PATCH 67/94] fixed nameing --- .../in/messaging/websub/ValidateIntentWebSubAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java index 8509b09..7bfb450 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java @@ -17,7 +17,7 @@ public class ValidateIntentWebSubAdapter { private String environment; @GetMapping(path = "/auction-started") - public ResponseEntity handleExecutorAddedEvent(@RequestParam("hub.challenge") String challenge) { + public ResponseEntity validateIntent(@RequestParam("hub.challenge") String challenge) { // Different implementation depending on local development or production if (environment.equalsIgnoreCase("development")) { HttpHeaders headers = new HttpHeaders(); -- 2.45.1 From 75feb5c4ae67566bada789f27a2df2e2cb641b11 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sun, 14 Nov 2021 23:33:23 +0100 Subject: [PATCH 68/94] Renaming and small refactoring --- .../common/clients/TapasMqttClient.java | 8 ++++---- .../ExecutorAddedEventListenerMqttAdapter.java | 4 ++-- ...ner.java => ExecutorEventMqttListener.java} | 2 +- ....java => ExecutorEventsMqttDispatcher.java} | 18 ++++-------------- ...xecutorRemovedEventListenerMqttAdapter.java | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) rename roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/{AuctionEventMqttListener.java => ExecutorEventMqttListener.java} (82%) rename roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/{AuctionEventsMqttDispatcher.java => ExecutorEventsMqttDispatcher.java} (61%) diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java index 8b5411b..78f2d0c 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java @@ -1,6 +1,6 @@ package ch.unisg.roster.roster.adapter.common.clients; -import ch.unisg.roster.roster.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; +import ch.unisg.roster.roster.adapter.in.messaging.mqtt.ExecutorEventsMqttDispatcher; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.*; @@ -25,9 +25,9 @@ public class TapasMqttClient { private final MessageReceivedCallback messageReceivedCallback; - private final AuctionEventsMqttDispatcher dispatcher; + private final ExecutorEventsMqttDispatcher dispatcher; - private TapasMqttClient(String brokerAddress, AuctionEventsMqttDispatcher dispatcher) { + private TapasMqttClient(String brokerAddress, ExecutorEventsMqttDispatcher dispatcher) { this.mqttClientId = UUID.randomUUID().toString(); this.brokerAddress = brokerAddress; @@ -37,7 +37,7 @@ public class TapasMqttClient { } public static synchronized TapasMqttClient getInstance(String brokerAddress, - AuctionEventsMqttDispatcher dispatcher) { + ExecutorEventsMqttDispatcher dispatcher) { if (tapasClient == null) { tapasClient = new TapasMqttClient(brokerAddress, dispatcher); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java index dd9257e..1c3cbcd 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java @@ -12,7 +12,7 @@ import ch.unisg.roster.roster.application.handler.ExecutorAddedHandler; import ch.unisg.roster.roster.application.port.in.ExecutorAddedEvent; import ch.unisg.roster.roster.domain.valueobject.ExecutorType; -public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListener { +public class ExecutorAddedEventListenerMqttAdapter extends ExecutorEventMqttListener { private static final Logger LOGGER = LogManager.getLogger(ExecutorAddedEventListenerMqttAdapter.class); @Override @@ -24,7 +24,7 @@ public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListe // representation that makes sense in the context of your application. JsonNode data = new ObjectMapper().readTree(payload); - String taskType = data.get("taskType").asText(); + String taskType = data.get("executorTaskType").asText(); String executorId = data.get("executorURI").asText(); ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventMqttListener.java similarity index 82% rename from roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventMqttListener.java index 6eb109f..df46b00 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventMqttListener.java @@ -5,7 +5,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; /** * Abstract MQTT listener for auction-related events */ -public abstract class AuctionEventMqttListener { +public abstract class ExecutorEventMqttListener { public abstract boolean handleEvent(MqttMessage message); } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java similarity index 61% rename from roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java index d19c803..caa6202 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java @@ -6,20 +6,10 @@ import java.util.Hashtable; import java.util.Map; import java.util.Set; -/** - * Dispatches MQTT messages for known topics to associated event listeners. Used in conjunction with - * {@link ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient}. - * - * This is where you would define MQTT topics and map them to event listeners (see - * {@link AuctionEventsMqttDispatcher#initRouter()}). - * - * This class is only provided as an example to help you bootstrap the project. You are welcomed to - * change this class as you see fit. - */ -public class AuctionEventsMqttDispatcher { - private final Map router; +public class ExecutorEventsMqttDispatcher { + private final Map router; - public AuctionEventsMqttDispatcher() { + public ExecutorEventsMqttDispatcher() { this.router = new Hashtable<>(); initRouter(); } @@ -46,7 +36,7 @@ public class AuctionEventsMqttDispatcher { * @param message the received MQTT message */ public void dispatchEvent(String topic, MqttMessage message) { - AuctionEventMqttListener listener = router.get(topic); + ExecutorEventMqttListener listener = router.get(topic); listener.handleEvent(message); } } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java index d7b5067..71af86d 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java @@ -11,7 +11,7 @@ import ch.unisg.common.valueobject.ExecutorURI; import ch.unisg.roster.roster.application.handler.ExecutorRemovedHandler; import ch.unisg.roster.roster.application.port.in.ExecutorRemovedEvent; -public class ExecutorRemovedEventListenerMqttAdapter extends AuctionEventMqttListener { +public class ExecutorRemovedEventListenerMqttAdapter extends ExecutorEventMqttListener { private static final Logger LOGGER = LogManager.getLogger(ExecutorRemovedEventListenerMqttAdapter.class); @Override -- 2.45.1 From 1c4da284800a7067d9fc00003bd8e28355d5557f Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 15 Nov 2021 11:59:27 +0100 Subject: [PATCH 69/94] fixed multiple bugs & updated cicd workflows --- .deployment/docker-compose.yml | 52 +++++++++---------- .github/workflows/build-and-deploy.yml | 29 ++++++----- .github/workflows/ci.assignment.yml | 41 --------------- ...cutor2.yml => ci.executor-computation.yml} | 12 +++-- ...ci.executor1.yml => ci.executor-robot.yml} | 10 ++-- .github/workflows/ci.roster.yml | 45 ++++++++++++++++ .../in/web/TaskAvailableController.java | 6 +++ .../web/ExecutionFinishedEventAdapter.java | 11 +++- .../adapter/out/web/GetAssignmentAdapter.java | 10 ++-- .../out/web/NotifyExecutorPoolAdapter.java | 7 +-- .../executor/domain/ExecutorBase.java | 13 +++-- .../src/main/resources/application.properties | 2 +- .../executor/domain/Executor.java | 16 +++--- .../ch/unisg/roster/RosterApplication.java | 28 ++++++++++ ...ExecutorAddedEventListenerMqttAdapter.java | 5 +- .../mqtt/ExecutorEventsMqttDispatcher.java | 4 +- .../adapter/in/web/NewTaskController.java | 8 +++ .../out/web/PublishNewTaskEventAdapter.java | 28 +++++----- .../application/service/NewTaskService.java | 1 + 19 files changed, 205 insertions(+), 123 deletions(-) delete mode 100644 .github/workflows/ci.assignment.yml rename .github/workflows/{ci.executor2.yml => ci.executor-computation.yml} (75%) rename .github/workflows/{ci.executor1.yml => ci.executor-robot.yml} (77%) create mode 100644 .github/workflows/ci.roster.yml diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 01a5a77..5a1329f 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -56,20 +56,20 @@ services: - "traefik.http.routers.tapas-auction-house.entryPoints=web,websecure" - "traefik.http.routers.tapas-auction-house.tls.certresolver=le" - assignment: + roster: image: openjdk - command: "java -jar /data/assignment-0.0.1-SNAPSHOT.jar" + command: "java -jar /data/roster-0.0.1-SNAPSHOT.jar" restart: unless-stopped volumes: - ./:/data/ labels: - "traefik.enable=true" - - "traefik.http.routers.assignment.rule=Host(`assignment.${PUB_IP}.nip.io`)" - - "traefik.http.routers.assignment.service=assignment" - - "traefik.http.services.assignment.loadbalancer.server.port=8082" - - "traefik.http.routers.assignment.tls=true" - - "traefik.http.routers.assignment.entryPoints=web,websecure" - - "traefik.http.routers.assignment.tls.certresolver=le" + - "traefik.http.routers.roster.rule=Host(`roster.${PUB_IP}.nip.io`)" + - "traefik.http.routers.roster.service=roster" + - "traefik.http.services.roster.loadbalancer.server.port=8082" + - "traefik.http.routers.roster.tls=true" + - "traefik.http.routers.roster.entryPoints=web,websecure" + - "traefik.http.routers.roster.tls.certresolver=le" executor-pool: image: openjdk @@ -86,38 +86,38 @@ services: - "traefik.http.routers.executor-pool.entryPoints=web,websecure" - "traefik.http.routers.executor-pool.tls.certresolver=le" - executor1: + executor-computation: image: openjdk - command: "java -jar /data/executor1-0.0.1-SNAPSHOT.jar" + command: "java -jar /data/executor-computation-0.0.1-SNAPSHOT.jar" restart: unless-stopped depends_on: - executor-pool - - assignment + - roster volumes: - ./:/data/ labels: - "traefik.enable=true" - - "traefik.http.routers.executor1.rule=Host(`executor1.${PUB_IP}.nip.io`)" - - "traefik.http.routers.executor1.service=executor1" - - "traefik.http.services.executor1.loadbalancer.server.port=8084" - - "traefik.http.routers.executor1.tls=true" - - "traefik.http.routers.executor1.entryPoints=web,websecure" - - "traefik.http.routers.executor1.tls.certresolver=le" + - "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)" + - "traefik.http.routers.executor-computation.service=executor-computation" + - "traefik.http.services.executor-computation.loadbalancer.server.port=8084" + - "traefik.http.routers.executor-computation.tls=true" + - "traefik.http.routers.executor-computation.entryPoints=web,websecure" + - "traefik.http.routers.executor-computation.tls.certresolver=le" - executor2: + executor-robot: image: openjdk - command: "java -jar /data/executor2-0.0.1-SNAPSHOT.jar" + command: "java -jar /data/executor-robot-0.0.1-SNAPSHOT.jar" restart: unless-stopped depends_on: - executor-pool - - assignment + - roster volumes: - ./:/data/ labels: - "traefik.enable=true" - - "traefik.http.routers.executor2.rule=Host(`executor2.${PUB_IP}.nip.io`)" - - "traefik.http.routers.executor2.service=executor2" - - "traefik.http.services.executor2.loadbalancer.server.port=8085" - - "traefik.http.routers.executor2.tls=true" - - "traefik.http.routers.executor2.entryPoints=web,websecure" - - "traefik.http.routers.executor2.tls.certresolver=le" + - "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)" + - "traefik.http.routers.executor-robot.service=executor-robot" + - "traefik.http.services.executor-robot.loadbalancer.server.port=8085" + - "traefik.http.routers.executor-robot.tls=true" + - "traefik.http.routers.executor-robot.entryPoints=web,websecure" + - "traefik.http.routers.executor-robot.tls.certresolver=le" diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index d223887..7290452 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -33,30 +33,33 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Build with Maven - run: mvn -f assignment/pom.xml --batch-mode --update-snapshots verify - - run: cp ./assignment/target/assignment-0.0.1-SNAPSHOT.jar ./target + - name: Build common library + run: mvn -f common/pom.xml --batch-mode --update-snapshots install - - name: Build with Maven + - name: Build roster service + run: mvn -f roster/pom.xml --batch-mode --update-snapshots verify + - run: cp ./roster/target/roster-0.0.1-SNAPSHOT.jar ./target + + - name: Build executor-pool service run: mvn -f executor-pool/pom.xml --batch-mode --update-snapshots verify - run: cp ./executor-pool/target/executor-pool-0.0.1-SNAPSHOT.jar ./target - - name: Build with Maven + - name: Build executor-base library run: mvn -f executor-base/pom.xml --batch-mode --update-snapshots install - - name: Build with Maven - run: mvn -f executor1/pom.xml --batch-mode --update-snapshots verify - - run: cp ./executor1/target/executor1-0.0.1-SNAPSHOT.jar ./target + - name: Build executor-computation service + run: mvn -f executor-computation/pom.xml --batch-mode --update-snapshots verify + - run: cp ./executor-computation/target/executor-computation-0.0.1-SNAPSHOT.jar ./target - - name: Build with Maven - run: mvn -f executor2/pom.xml --batch-mode --update-snapshots verify - - run: cp ./executor2/target/executor2-0.0.1-SNAPSHOT.jar ./target + - name: Build executor-robot service + 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 - - name: Build with Maven + - name: Build tapas-task service 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 - - name: Build with Maven + - name: Build tapas-auction-house service run: mvn -f tapas-auction-house/pom.xml --batch-mode --update-snapshots verify - run: cp ./tapas-auction-house/target/tapas-auction-house-0.0.1-SNAPSHOT.jar ./target diff --git a/.github/workflows/ci.assignment.yml b/.github/workflows/ci.assignment.yml deleted file mode 100644 index 394fe54..0000000 --- a/.github/workflows/ci.assignment.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: CI Assignment -on: - push: - branches: [main, dev] - paths: - - "assignment/**" - pull_request: - branches: [main, dev] - paths: - - "assignment/**" - - workflow_dispatch: -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f assignment/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-assignment diff --git a/.github/workflows/ci.executor2.yml b/.github/workflows/ci.executor-computation.yml similarity index 75% rename from .github/workflows/ci.executor2.yml rename to .github/workflows/ci.executor-computation.yml index 5ae38f0..5832883 100644 --- a/.github/workflows/ci.executor2.yml +++ b/.github/workflows/ci.executor-computation.yml @@ -1,15 +1,17 @@ -name: CI Executor 2 +name: CI executor-computation on: push: branches: [main, dev] paths: - "executor-base/**" - - "executor2/**" + - "executor-computation/**" + - "common/**" pull_request: branches: [main, dev] paths: - "executor-base/**" - - "executor2/**" + - "executor-computation/**" + - "common/**" workflow_dispatch: jobs: @@ -36,10 +38,12 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 + - name: Build common + run: mvn -f common/pom.xml -B install - name: Build executorBase run: mvn -f executor-base/pom.xml -B install - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f executor2/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor2 + run: mvn -f executor-computation/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor2 diff --git a/.github/workflows/ci.executor1.yml b/.github/workflows/ci.executor-robot.yml similarity index 77% rename from .github/workflows/ci.executor1.yml rename to .github/workflows/ci.executor-robot.yml index 708d7d4..2ccf607 100644 --- a/.github/workflows/ci.executor1.yml +++ b/.github/workflows/ci.executor-robot.yml @@ -4,12 +4,14 @@ on: branches: [main, dev] paths: - "executor-base/**" - - "executor1/**" + - "executor-robot/**" + - "common/**" pull_request: branches: [main, dev] paths: - "executor-base/**" - - "executor1/**" + - "executor-robot/**" + - "common/**" workflow_dispatch: jobs: @@ -36,10 +38,12 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 + - name: Build executorBase + run: mvn -f common/pom.xml -B install - name: Build executorBase run: mvn -f executor-base/pom.xml -B install - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -f executor1/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor1 + run: mvn -f executor-robot/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-executor1 diff --git a/.github/workflows/ci.roster.yml b/.github/workflows/ci.roster.yml new file mode 100644 index 0000000..b38355e --- /dev/null +++ b/.github/workflows/ci.roster.yml @@ -0,0 +1,45 @@ +name: CI Roster +on: + push: + branches: [main, dev] + paths: + - "roster/**" + - "common/**" + pull_request: + branches: [main, dev] + paths: + - "roster/**" + - "common/**" + + workflow_dispatch: +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build common package + run: mvn -f common/pom.xml -B install + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -f roster/pom.xml -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=scs-asse-fs21-group1_tapas-assignment diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java index 8fda5ac..66ef496 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java @@ -1,5 +1,7 @@ package ch.unisg.executorbase.executor.adapter.in.web; +import java.util.logging.Logger; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -19,6 +21,8 @@ public class TaskAvailableController { this.taskAvailableUseCase = taskAvailableUseCase; } + Logger logger = Logger.getLogger(TaskAvailableController.class.getName()); + /** * Controller for notification about new events. * @return 200 OK @@ -26,6 +30,8 @@ public class TaskAvailableController { @GetMapping(path = "/newtask/{taskType}", consumes = { "application/json" }) public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { + logger.info("New " + taskType + " available"); + if (ExecutorType.contains(taskType.toUpperCase())) { TaskAvailableCommand command = new TaskAvailableCommand( ExecutorType.valueOf(taskType.toUpperCase())); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index a5ae910..58f6287 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -16,8 +16,9 @@ import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { + // TODO url doesn't get mapped bc no autowiring @Value("${roster.url}") - String server; + String server = "http://localhost:8082"; Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName()); @@ -28,6 +29,9 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort @Override public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { + System.out.println("HI"); + System.out.println(server); + String body = new JSONObject() .put("taskID", event.getTaskID()) .put("result", event.getResult()) @@ -41,6 +45,9 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); + + System.out.println(server); + try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (InterruptedException e) { @@ -50,7 +57,7 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } - logger.log(Level.INFO, "Finish execution event sent with result: {}", event.getResult()); + logger.log(Level.INFO, "Finish execution event sent with result: {0}", event.getResult()); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index 3ed4e37..dd82c81 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -23,8 +23,9 @@ import org.json.JSONObject; @Primary public class GetAssignmentAdapter implements GetAssignmentPort { + // TODO Not working for now bc it doesn't get autowired @Value("${roster.url}") - String server; + String server = "http://127.0.0.1:8082"; Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName()); @@ -51,12 +52,15 @@ public class GetAssignmentAdapter implements GetAssignmentPort { try { logger.info("Sending getAssignment Request"); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - logger.log(Level.INFO, "getAssignment request result:\n {}", response.body()); + logger.log(Level.INFO, "getAssignment request result:\n {0}", response.body()); if (response.body().equals("")) { return null; } JSONObject responseBody = new JSONObject(response.body()); - return new Task(responseBody.getString("taskID"), responseBody.getString("input")); + + String[] input = { "1", "+", "2" }; + // TODO Add input in roster + tasklist + return new Task(responseBody.getString("taskID"), input); } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index 2dba64f..abc0cf5 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -22,8 +22,9 @@ import ch.unisg.executorbase.executor.domain.ExecutorType; @Primary public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { - @Value("${executor-pool.url}") - String server; + // TODO Not working for now bc it doesn't get autowired + @Value("${executor.pool.url}") + String server = "http://127.0.0.1:8083"; Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); @@ -36,7 +37,7 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { String body = new JSONObject() .put("executorTaskType", executorType) - .put("executorURI", executorURI.getValue()) + .put("executorUri", executorURI.getValue()) .toString(); HttpClient client = HttpClient.newHttpClient(); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index 122ea4b..b8e8631 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -23,9 +23,8 @@ public abstract class ExecutorBase { @Getter private ExecutorStatus status; - // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model - // TODO I guess we can implement the execution as a service but there still is the problem with the startup request. - // TODO I guess we can somehow autowire this but I don't know why it's not working :D + // 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(); @@ -38,8 +37,8 @@ public abstract class ExecutorBase { this.status = ExecutorStatus.STARTING_UP; this.executorType = executorType; // TODO set this automaticly - this.executorURI = new ExecutorURI("localhost:8084"); - + 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)) { System.exit(0); @@ -55,6 +54,8 @@ public abstract class ExecutorBase { **/ public void getAssignment() { Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getExecutorURI()); + System.out.println("New assignment"); + System.out.println(newTask); if (newTask != null) { this.executeTask(newTask); } else { @@ -72,6 +73,8 @@ public abstract class ExecutorBase { task.setResult(execution(task.getInput())); + System.out.println(task.getResult()); + // TODO implement logic if execution was not successful executionFinishedEventPort.publishExecutionFinishedEvent( new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); diff --git a/executor-base/src/main/resources/application.properties b/executor-base/src/main/resources/application.properties index 3eee96a..4316ebf 100644 --- a/executor-base/src/main/resources/application.properties +++ b/executor-base/src/main/resources/application.properties @@ -1,6 +1,6 @@ server.port=8081 roster.url=http://127.0.0.1:8082 -executor-pool.url=http://127.0.0.1:8083 +executor.pool.url=http://127.0.0.1:8083 executor1.url=http://127.0.0.1:8084 executor2.url=http://127.0.0.1:8085 task-list.url=http://127.0.0.1:8081 diff --git a/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java b/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java index eb09699..c3fbb67 100644 --- a/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java +++ b/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java @@ -21,16 +21,18 @@ public class Executor extends ExecutorBase { protected String execution(String... input) { + System.out.println(input); + double result = Double.NaN; int a = Integer.parseInt(input[0]); int b = Integer.parseInt(input[2]); String operation = input[1]; - try { - TimeUnit.SECONDS.sleep(20); - } catch (InterruptedException e) { - e.printStackTrace(); - } + // try { + // TimeUnit.SECONDS.sleep(20); + // } catch (InterruptedException e) { + // e.printStackTrace(); + // } if (operation == "+") { result = a + b; @@ -40,7 +42,9 @@ public class Executor extends ExecutorBase { result = a - b; } + System.out.println("finish"); + return Double.toString(result); } -} \ No newline at end of file +} diff --git a/roster/src/main/java/ch/unisg/roster/RosterApplication.java b/roster/src/main/java/ch/unisg/roster/RosterApplication.java index dd57a5d..bc18c54 100644 --- a/roster/src/main/java/ch/unisg/roster/RosterApplication.java +++ b/roster/src/main/java/ch/unisg/roster/RosterApplication.java @@ -1,13 +1,41 @@ package ch.unisg.roster; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import ch.unisg.roster.roster.adapter.common.clients.TapasMqttClient; +import ch.unisg.roster.roster.adapter.in.messaging.mqtt.ExecutorEventMqttListener; +import ch.unisg.roster.roster.adapter.in.messaging.mqtt.ExecutorEventsMqttDispatcher; + @SpringBootApplication public class RosterApplication { + static Logger logger = Logger.getLogger(RosterApplication.class.getName()); + + public static String MQTT_BROKER = "tcp://localhost:1883"; + public static void main(String[] args) { SpringApplication.run(RosterApplication.class, args); + + bootstrapMarketplaceWithMqtt(); } + /** + * Connects to an MQTT broker, presumably the one used by all TAPAS groups to communicate with + * one another + */ + private static void bootstrapMarketplaceWithMqtt() { + try { + ExecutorEventsMqttDispatcher dispatcher = new ExecutorEventsMqttDispatcher(); + TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + client.startReceivingMessages(); + } catch (MqttException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java index 1c3cbcd..10e907e 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java @@ -17,6 +17,9 @@ public class ExecutorAddedEventListenerMqttAdapter extends ExecutorEventMqttList @Override public boolean handleEvent(MqttMessage message) { + + System.out.println("New Executor added!"); + String payload = new String(message.getPayload()); try { @@ -25,7 +28,7 @@ public class ExecutorAddedEventListenerMqttAdapter extends ExecutorEventMqttList JsonNode data = new ObjectMapper().readTree(payload); String taskType = data.get("executorTaskType").asText(); - String executorId = data.get("executorURI").asText(); + String executorId = data.get("executorUri").asText(); ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( new ExecutorURI(executorId), diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java index caa6202..c1b7649 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorEventsMqttDispatcher.java @@ -16,8 +16,8 @@ public class ExecutorEventsMqttDispatcher { // TODO: Register here your topics and event listener adapters private void initRouter() { - router.put("ch/unisg/tapas-group-tutors/executors/added", new ExecutorAddedEventListenerMqttAdapter()); - router.put("ch/unisg/tapas-group-tutors/executors/removed", new ExecutorRemovedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas/executors/added", new ExecutorAddedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas/executors/removed", new ExecutorRemovedEventListenerMqttAdapter()); } /** diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java index af01346..98b3ac7 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java @@ -1,5 +1,7 @@ package ch.unisg.roster.roster.adapter.in.web; +import java.util.logging.Logger; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -18,6 +20,8 @@ public class NewTaskController { this.newTaskUseCase = newTaskUseCase; } + Logger logger = Logger.getLogger(NewTaskController.class.getName()); + /** * Controller which handles the new task event from the tasklist * @return 201 Create or 409 Conflict @@ -25,10 +29,14 @@ public class NewTaskController { @PostMapping(path = "/task", consumes = {"application/task+json"}) public ResponseEntity newTaskController(@RequestBody Task task) { + logger.info("New task with id:" + task.getTaskID()); + NewTaskCommand command = new NewTaskCommand(task.getTaskID(), task.getTaskType()); boolean success = newTaskUseCase.addNewTaskToQueue(command); + logger.info("Could create task: " + success); + if (success) { return new ResponseEntity<>(HttpStatus.CREATED); } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java index 6a6b7f7..0b16567 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java @@ -34,21 +34,23 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { @Override public void publishNewTaskEvent(NewTaskEvent event) { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/newtask/" + event.taskType.getValue())) - .GET() - .build(); + System.out.println(server2); + + // HttpClient client = HttpClient.newHttpClient(); + // HttpRequest request = HttpRequest.newBuilder() + // .uri(URI.create(server + "/newtask/" + event.taskType.getValue())) + // .GET() + // .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); - } + // 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); + // } HttpClient client2 = HttpClient.newHttpClient(); HttpRequest request2 = HttpRequest.newBuilder() diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java index 588ed04..c6e1685 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java @@ -29,6 +29,7 @@ public class NewTaskService implements NewTaskUseCase { public boolean addNewTaskToQueue(NewTaskCommand command) { ExecutorRegistry executorRegistry = ExecutorRegistry.getInstance(); + if (!executorRegistry.containsTaskType(command.getTaskType())) { return false; } -- 2.45.1 From 4c5da8eed6557df6549b2d4334c4357d001c8721 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 15 Nov 2021 12:03:25 +0100 Subject: [PATCH 70/94] fix naming --- .github/workflows/ci.executor-robot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.executor-robot.yml b/.github/workflows/ci.executor-robot.yml index 2ccf607..7a216aa 100644 --- a/.github/workflows/ci.executor-robot.yml +++ b/.github/workflows/ci.executor-robot.yml @@ -1,4 +1,4 @@ -name: CI Executor 1 +name: CI executor-robot on: push: branches: [main, dev] -- 2.45.1 From bce36196384416cc30a626ba1b595530e224c362 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Nov 2021 15:48:46 +0100 Subject: [PATCH 71/94] renaming --- executor-base/pom.xml | 4 ++-- .../.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 .../Dockerfile | 0 {executorcomputation => executor-computation}/mvnw | 0 .../mvnw.cmd | 0 .../pom.xml | 6 +++--- .../ExecutorcomputationApplication.java | 0 .../adapter/in/web/TaskAvailableController.java | 0 .../application/service/TaskAvailableService.java | 0 .../executor/domain/Executor.java | 0 .../src/main/resources/application.properties | 0 .../ExecutorcomputationApplicationTests.java | 0 {executorrobot => executor-robot}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {executorrobot => executor-robot}/Dockerfile | 0 {executorrobot => executor-robot}/mvnw | 0 {executorrobot => executor-robot}/mvnw.cmd | 0 {executorrobot => executor-robot}/pom.xml | 6 +++--- .../executorrobot/ExecutorrobotApplication.java | 0 .../adapter/in/web/TaskAvailableController.java | 0 .../adapter/out/DeleteUserFromRobotAdapter.java | 0 .../adapter/out/InstructionToRobotAdapter.java | 0 .../executor/adapter/out/UserToRobotAdapter.java | 0 .../port/out/DeleteUserFromRobotPort.java | 0 .../port/out/InstructionToRobotPort.java | 0 .../application/port/out/UserToRobotPort.java | 0 .../application/service/TaskAvailableService.java | 0 .../executorrobot/executor/domain/Executor.java | 0 .../src/main/resources/application.properties | 0 .../ExecutorrobotApplicationTests.java | 0 .../ExecutorRemovedEventListenerMqttAdapter.java | 2 +- .../adapter/out/web/PublishNewTaskEventAdapter.java | 2 -- 37 files changed, 9 insertions(+), 11 deletions(-) rename {executorcomputation => executor-computation}/.gitignore (100%) rename {executorcomputation => executor-computation}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {executorcomputation => executor-computation}/.mvn/wrapper/maven-wrapper.jar (100%) rename {executorcomputation => executor-computation}/.mvn/wrapper/maven-wrapper.properties (100%) rename {executorcomputation => executor-computation}/Dockerfile (100%) rename {executorcomputation => executor-computation}/mvnw (100%) rename {executorcomputation => executor-computation}/mvnw.cmd (100%) rename {executorcomputation => executor-computation}/pom.xml (94%) rename {executorcomputation => executor-computation}/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java (100%) rename {executorcomputation => executor-computation}/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java (100%) rename {executorcomputation => executor-computation}/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java (100%) rename {executorcomputation => executor-computation}/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java (100%) rename {executorcomputation => executor-computation}/src/main/resources/application.properties (100%) rename {executorcomputation => executor-computation}/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java (100%) rename {executorrobot => executor-robot}/.gitignore (100%) rename {executorrobot => executor-robot}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {executorrobot => executor-robot}/.mvn/wrapper/maven-wrapper.jar (100%) rename {executorrobot => executor-robot}/.mvn/wrapper/maven-wrapper.properties (100%) rename {executorrobot => executor-robot}/Dockerfile (100%) rename {executorrobot => executor-robot}/mvnw (100%) rename {executorrobot => executor-robot}/mvnw.cmd (100%) rename {executorrobot => executor-robot}/pom.xml (94%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java (100%) rename {executorrobot => executor-robot}/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java (100%) rename {executorrobot => executor-robot}/src/main/resources/application.properties (100%) rename {executorrobot => executor-robot}/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java (100%) diff --git a/executor-base/pom.xml b/executor-base/pom.xml index 7893d45..4ea8d2a 100644 --- a/executor-base/pom.xml +++ b/executor-base/pom.xml @@ -9,9 +9,9 @@ ch.unisg - executorbase + executor-base 0.0.1-SNAPSHOT - executorbase + executor-base Demo project for Spring Boot 11 diff --git a/executorcomputation/.gitignore b/executor-computation/.gitignore similarity index 100% rename from executorcomputation/.gitignore rename to executor-computation/.gitignore diff --git a/executorcomputation/.mvn/wrapper/MavenWrapperDownloader.java b/executor-computation/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from executorcomputation/.mvn/wrapper/MavenWrapperDownloader.java rename to executor-computation/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/executorcomputation/.mvn/wrapper/maven-wrapper.jar b/executor-computation/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from executorcomputation/.mvn/wrapper/maven-wrapper.jar rename to executor-computation/.mvn/wrapper/maven-wrapper.jar diff --git a/executorcomputation/.mvn/wrapper/maven-wrapper.properties b/executor-computation/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from executorcomputation/.mvn/wrapper/maven-wrapper.properties rename to executor-computation/.mvn/wrapper/maven-wrapper.properties diff --git a/executorcomputation/Dockerfile b/executor-computation/Dockerfile similarity index 100% rename from executorcomputation/Dockerfile rename to executor-computation/Dockerfile diff --git a/executorcomputation/mvnw b/executor-computation/mvnw similarity index 100% rename from executorcomputation/mvnw rename to executor-computation/mvnw diff --git a/executorcomputation/mvnw.cmd b/executor-computation/mvnw.cmd similarity index 100% rename from executorcomputation/mvnw.cmd rename to executor-computation/mvnw.cmd diff --git a/executorcomputation/pom.xml b/executor-computation/pom.xml similarity index 94% rename from executorcomputation/pom.xml rename to executor-computation/pom.xml index b9e45ac..c319081 100644 --- a/executorcomputation/pom.xml +++ b/executor-computation/pom.xml @@ -9,9 +9,9 @@ ch.unisg - executorcomputation + executor-computation 0.0.1-SNAPSHOT - executorcomputation + executor-computation Demo project for Spring Boot 11 @@ -42,7 +42,7 @@ ch.unisg - executorbase + executor-base 0.0.1-SNAPSHOT compile diff --git a/executorcomputation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java rename to executor-computation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java diff --git a/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java rename to executor-computation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java diff --git a/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java rename to executor-computation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java diff --git a/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java rename to executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java diff --git a/executorcomputation/src/main/resources/application.properties b/executor-computation/src/main/resources/application.properties similarity index 100% rename from executorcomputation/src/main/resources/application.properties rename to executor-computation/src/main/resources/application.properties diff --git a/executorcomputation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java b/executor-computation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java similarity index 100% rename from executorcomputation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java rename to executor-computation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java diff --git a/executorrobot/.gitignore b/executor-robot/.gitignore similarity index 100% rename from executorrobot/.gitignore rename to executor-robot/.gitignore diff --git a/executorrobot/.mvn/wrapper/MavenWrapperDownloader.java b/executor-robot/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from executorrobot/.mvn/wrapper/MavenWrapperDownloader.java rename to executor-robot/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/executorrobot/.mvn/wrapper/maven-wrapper.jar b/executor-robot/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from executorrobot/.mvn/wrapper/maven-wrapper.jar rename to executor-robot/.mvn/wrapper/maven-wrapper.jar diff --git a/executorrobot/.mvn/wrapper/maven-wrapper.properties b/executor-robot/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from executorrobot/.mvn/wrapper/maven-wrapper.properties rename to executor-robot/.mvn/wrapper/maven-wrapper.properties diff --git a/executorrobot/Dockerfile b/executor-robot/Dockerfile similarity index 100% rename from executorrobot/Dockerfile rename to executor-robot/Dockerfile diff --git a/executorrobot/mvnw b/executor-robot/mvnw similarity index 100% rename from executorrobot/mvnw rename to executor-robot/mvnw diff --git a/executorrobot/mvnw.cmd b/executor-robot/mvnw.cmd similarity index 100% rename from executorrobot/mvnw.cmd rename to executor-robot/mvnw.cmd diff --git a/executorrobot/pom.xml b/executor-robot/pom.xml similarity index 94% rename from executorrobot/pom.xml rename to executor-robot/pom.xml index 101c268..ca95edf 100644 --- a/executorrobot/pom.xml +++ b/executor-robot/pom.xml @@ -9,9 +9,9 @@ ch.unisg - executorrobot + executor-robot 0.0.1-SNAPSHOT - executorrobot + executor-robot Demo project for Spring Boot 11 @@ -42,7 +42,7 @@ ch.unisg - executorbase + executor-base 0.0.1-SNAPSHOT diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java b/executor-robot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java diff --git a/executorrobot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java rename to executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java diff --git a/executorrobot/src/main/resources/application.properties b/executor-robot/src/main/resources/application.properties similarity index 100% rename from executorrobot/src/main/resources/application.properties rename to executor-robot/src/main/resources/application.properties diff --git a/executorrobot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java b/executor-robot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java similarity index 100% rename from executorrobot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java rename to executor-robot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java index 71af86d..0d0923a 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java @@ -23,7 +23,7 @@ public class ExecutorRemovedEventListenerMqttAdapter extends ExecutorEventMqttLi // representation that makes sense in the context of your application. JsonNode data = new ObjectMapper().readTree(payload); - String executorId = data.get("executorURI").asText(); + String executorId = data.get("executorUri").asText(); ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( new ExecutorURI(executorId)); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java index 0b16567..274d639 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java @@ -34,8 +34,6 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { @Override public void publishNewTaskEvent(NewTaskEvent event) { - System.out.println(server2); - // HttpClient client = HttpClient.newHttpClient(); // HttpRequest request = HttpRequest.newBuilder() // .uri(URI.create(server + "/newtask/" + event.taskType.getValue())) -- 2.45.1 From df53236853a47969c82ef6ce312968841bd2ff87 Mon Sep 17 00:00:00 2001 From: Peter Guhl Date: Tue, 16 Nov 2021 16:12:15 +0100 Subject: [PATCH 72/94] Compiling and running tapas on a local docker installation (#10) * First version building tasks and mongodb * Creating container for local tests * Initial version * Added mongodb to correct network * Added auction house * Acknowledgments, Dockerfile for app * Added app to build * Dockerfile for app * Mapped app port to localhost:8080 Co-authored-by: Ubuntu --- app/Dockerfile | 13 ++++++++ docker-compose-local.yml | 60 ++++++++++++++++++++++++++++++++++ tapas-auction-house/Dockerfile | 13 ++++++++ tapas-tasks/Dockerfile | 13 ++++++++ 4 files changed, 99 insertions(+) create mode 100644 app/Dockerfile create mode 100644 docker-compose-local.yml create mode 100644 tapas-auction-house/Dockerfile create mode 100644 tapas-tasks/Dockerfile diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..429a177 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,13 @@ +# 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 diff --git a/docker-compose-local.yml b/docker-compose-local.yml new file mode 100644 index 0000000..1ddfc24 --- /dev/null +++ b/docker-compose-local.yml @@ -0,0 +1,60 @@ +# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021) + +version: "3.7" + +services: + app: + build: + context: ./app + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside the docker network + - "8080:8080" + networks: + - tapas-network + + tapas-tasks: + build: + context: ./tapas-tasks + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside + - "8081:8081" + networks: + - tapas-network + + tapas-auction-house: + build: + context: ./tapas-auction-house + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside + - "8082:8082" + networks: + - tapas-network + + mongodb: + image: mongo + container_name: mongodb + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: 8nP7s0a # Can not be changed again later on + volumes: + - database:/data/db + networks: + - tapas-network + +#Volume for mongodb. One per server. +volumes: + database: + +networks: + tapas-network: + driver: bridge diff --git a/tapas-auction-house/Dockerfile b/tapas-auction-house/Dockerfile new file mode 100644 index 0000000..fcc6d83 --- /dev/null +++ b/tapas-auction-house/Dockerfile @@ -0,0 +1,13 @@ +# 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/tapas-auction-house-0.0.1-SNAPSHOT.jar ./tapas-auction-house-0.0.1-SNAPSHOT.jar + +CMD java -jar tapas-auction-house-0.0.1-SNAPSHOT.jar diff --git a/tapas-tasks/Dockerfile b/tapas-tasks/Dockerfile new file mode 100644 index 0000000..b05be85 --- /dev/null +++ b/tapas-tasks/Dockerfile @@ -0,0 +1,13 @@ +# 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/tapas-tasks-0.0.1-SNAPSHOT.jar ./tapas-tasks-0.0.1-SNAPSHOT.jar + +CMD java -jar tapas-tasks-0.0.1-SNAPSHOT.jar -- 2.45.1 From 8fba9136b2abfa65aa095852f0abb80860297803 Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 16 Nov 2021 17:42:14 +0100 Subject: [PATCH 73/94] Implemented auctioning of tasks workflow in auction house --- .../formats/AuctionJsonRepresentation.java | 5 +- .../common/formats/BidJsonRepresentation.java | 43 ++++++ .../formats/TaskJsonRepresentation.java | 115 +++++++++++++++++ .../BidReceivedEventListenerMqttAdapter.java | 52 ++++++++ .../adapter/in/web/AddBidWebController.java | 38 ++++++ ...PublishAuctionStartedEventMqttAdapter.java | 2 +- .../out/web/AuctionWonEventHttpAdapter.java | 60 +++++++++ .../handler/BidReceivedHandler.java | 17 +++ .../application/port/in/BidReceivedEvent.java | 17 +++ .../port/in/BidReceivedEventHandler.java | 5 + .../unisg/tapas/auctionhouse/domain/Task.java | 122 ++++++++++++++++++ .../src/main/resources/application.properties | 2 +- 12 files changed, 474 insertions(+), 4 deletions(-) create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/BidJsonRepresentation.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/BidReceivedEventListenerMqttAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java rename tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/{websub => mqtt}/PublishAuctionStartedEventMqttAdapter.java (95%) create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/BidReceivedHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEvent.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEventHandler.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Task.java diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java index ea4cf2c..757c8c8 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/AuctionJsonRepresentation.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.Setter; import java.sql.Timestamp; +import java.text.SimpleDateFormat; /** * Used to expose a representation of the state of an auction through an interface. This class is @@ -15,7 +16,7 @@ import java.sql.Timestamp; * to modify this class as you see fit! */ public class AuctionJsonRepresentation { - public static final String MEDIA_TYPE = "application/json"; + public static final String MEDIA_TYPE = "application/auction+json"; @Getter @Setter private String auctionId; @@ -56,7 +57,7 @@ public class AuctionJsonRepresentation { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - + mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); return mapper.writeValueAsString(representation); } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/BidJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/BidJsonRepresentation.java new file mode 100644 index 0000000..7ae3dda --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/BidJsonRepresentation.java @@ -0,0 +1,43 @@ +package ch.unisg.tapas.auctionhouse.adapter.common.formats; + +import ch.unisg.tapas.auctionhouse.domain.Bid; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +import java.text.SimpleDateFormat; + +public class BidJsonRepresentation { + public static final String MEDIA_TYPE = "application/bid+json"; + + @Getter @Setter + private String auctionId; + + @Getter @Setter + private String bidderName; + + @Getter @Setter + private String bidderAuctionHouseUri; + + @Getter @Setter + private String bidderTaskListUri; + + public BidJsonRepresentation() {} + + public BidJsonRepresentation(Bid bid){ + this.auctionId = bid.getAuctionId().getValue(); + this.bidderName = bid.getBidderName().getValue(); + this.bidderAuctionHouseUri = bid.getBidderAuctionHouseUri().getValue().toString(); + this.bidderTaskListUri = bid.getBidderTaskListUri().getValue().toString(); + } + + public static String serialize(Bid bid) throws JsonProcessingException { + BidJsonRepresentation representation = new BidJsonRepresentation(bid); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.writeValueAsString(representation); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java new file mode 100644 index 0000000..467c550 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java @@ -0,0 +1,115 @@ +package ch.unisg.tapas.auctionhouse.adapter.common.formats; + +import ch.unisg.tapas.auctionhouse.domain.Task; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +/** + * This class is used to expose and consume representations of tasks through the HTTP interface. The + * representations conform to the custom JSON-based media type "application/task+json". The media type + * is just an identifier and can be registered with + * IANA to promote interoperability. + */ +final public class TaskJsonRepresentation { + // The media type used for this task representation format + public static final String MEDIA_TYPE = "application/task+json"; + + // A task identifier specific to our implementation (e.g., a UUID). This identifier is then used + // to generate the task's URI. URIs are standard uniform identifiers and use a universal syntax + // that can be referenced (and dereferenced) independent of context. In our uniform HTTP API, + // we identify tasks via URIs and not implementation-specific identifiers. + @Getter @Setter + private String taskId; + + // A string that represents the task's name + @Getter + private final String taskName; + + // A string that identifies the task's type. This string could also be a URI (e.g., defined in some + // Web ontology, as we shall see later in the course), but it's not constrained to be a URI. + // The task's type can be used to assign executors to tasks, to decide what tasks to bid for, etc. + @Getter + private final String taskType; + + // The task's status: OPEN, ASSIGNED, RUNNING, or EXECUTED (see Task.Status) + @Getter @Setter + private String taskStatus; + + // If this task is a delegated task (i.e., a shadow of another task), this URI points to the + // original task. Because URIs are standard and uniform, we can just dereference this URI to + // retrieve a representation of the original task. + @Getter @Setter + private String originalTaskUri; + + // The service provider who executes this task. The service provider is a any string that identifies + // a TAPAS group (e.g., tapas-group1). This identifier could also be a URI (if we have a good reason + // for it), but it's not constrained to be a URI. + @Getter @Setter + private String serviceProvider; + + // A string that provides domain-specific input data for this task. In the context of this project, + // we can parse and interpret the input data based on the task's type. + @Getter @Setter + private String inputData; + + // A string that provides domain-specific output data for this task. In the context of this project, + // we can parse and interpret the output data based on the task's type. + @Getter @Setter + private String outputData; + + /** + * Instantiate a task representation with a task name and type. + * + * @param taskName string that represents the task's name + * @param taskType string that represents the task's type + */ + public TaskJsonRepresentation(String taskName, String taskType) { + this.taskName = taskName; + this.taskType = taskType; + + this.taskStatus = null; + this.originalTaskUri = null; + this.serviceProvider = null; + this.inputData = null; + this.outputData = null; + } + + /** + * Instantiate a task representation from a domain concept. + * + * @param task the task + */ + public TaskJsonRepresentation(Task task) { + this(task.getTaskName().getValue(), task.getTaskType().getValue()); + + this.taskId = task.getTaskId().getValue(); + this.taskStatus = task.getTaskStatus().getValue().name(); + + this.originalTaskUri = (task.getOriginalTaskUri() == null) ? + null : task.getOriginalTaskUri().getValue(); + + this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue(); + this.inputData = (task.getInputData() == null) ? null : task.getInputData().getValue(); + this.outputData = (task.getOutputData() == null) ? null : task.getOutputData().getValue(); + } + + /** + * Convenience method used to serialize a task provided as a domain concept in the format exposed + * through the uniform HTTP API. + * + * @param task the task as defined in the domain + * @return a string serialization using the JSON-based representation format defined for tasks + * @throws JsonProcessingException if a runtime exception occurs during object serialization + */ + public static String serialize(Task task) throws JsonProcessingException { + TaskJsonRepresentation representation = new TaskJsonRepresentation(task); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return mapper.writeValueAsString(representation); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/BidReceivedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/BidReceivedEventListenerMqttAdapter.java new file mode 100644 index 0000000..29f45da --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/BidReceivedEventListenerMqttAdapter.java @@ -0,0 +1,52 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler; +import ch.unisg.tapas.auctionhouse.application.handler.ExecutorAddedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Bid; +import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.net.URI; + +public class BidReceivedEventListenerMqttAdapter extends AuctionEventMqttListener { + private static final Logger LOGGER = LogManager.getLogger(BidReceivedEventListenerMqttAdapter.class); + + @Override + public boolean handleEvent(MqttMessage message){ + String payload = new String(message.getPayload()); + + try { + // Note: this message representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String auctionId = data.get("auctionId").asText(); + String bidderName = data.get("bidderName").asText(); + String bidderAuctionHouseUri = data.get("bidderAuctionHouseUri").asText(); + String bidderTaskListUri = data.get("bidderTaskListUri").asText(); + + BidReceivedEvent bidReceivedEvent = new BidReceivedEvent( new Bid( + new Auction.AuctionId(auctionId), + new Bid.BidderName(bidderName), + new Bid.BidderAuctionHouseUri(URI.create(bidderAuctionHouseUri)), + new Bid.BidderTaskListUri(URI.create(bidderTaskListUri)) + )); + + BidReceivedHandler bidReceivedHandler = new BidReceivedHandler(); + bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent); + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + + return true; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java new file mode 100644 index 0000000..3431c8d --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java @@ -0,0 +1,38 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.web; + +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.BidJsonRepresentation; +import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Bid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +// TODO Fix structure due to MQTT +@RestController +public class AddBidWebController { + @PostMapping(path = "/bid", consumes = BidJsonRepresentation.MEDIA_TYPE) + public ResponseEntity addBid(@RequestBody BidJsonRepresentation payload) { + BidReceivedEvent bidReceivedEvent = new BidReceivedEvent(new Bid( + new Auction.AuctionId(payload.getAuctionId()), + new Bid.BidderName(payload.getBidderName()), + new Bid.BidderAuctionHouseUri(URI.create(payload.getBidderAuctionHouseUri())), + new Bid.BidderTaskListUri(URI.create(payload.getBidderTaskListUri())) + )); + + BidReceivedHandler bidReceivedHandler = new BidReceivedHandler(); + bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent); + + HttpHeaders responseHeaders = new HttpHeaders(); + + return new ResponseEntity<>(responseHeaders, HttpStatus.NO_CONTENT); + } + +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/mqtt/PublishAuctionStartedEventMqttAdapter.java similarity index 95% rename from tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java rename to tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/mqtt/PublishAuctionStartedEventMqttAdapter.java index d5bb0fc..a041b4f 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/mqtt/PublishAuctionStartedEventMqttAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.tapas.auctionhouse.adapter.out.messaging.websub; +package ch.unisg.tapas.auctionhouse.adapter.out.messaging.mqtt; import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient; import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java index 26949f2..4583892 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java @@ -1,10 +1,24 @@ package ch.unisg.tapas.auctionhouse.adapter.out.web; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.TaskJsonRepresentation; +import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler; import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort; +import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry; import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent; +import ch.unisg.tapas.auctionhouse.domain.Task; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + /** * This class is a template for sending auction won events via HTTP. This class was created here only * as a placeholder, it is up to you to decide how such events should be sent (e.g., via HTTP, @@ -13,8 +27,54 @@ import org.springframework.stereotype.Component; @Component @Primary public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { + private static final Logger LOGGER = LogManager.getLogger(AuctionWonEventHttpAdapter.class); + + @Value("${tasks.list.uri}") + String server; + @Override public void publishAuctionWonEvent(AuctionWonEvent event) { + try{ + var auction = AuctionRegistry.getInstance().getAuctionById(event.getWinningBid().getAuctionId()); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(auction.get().getTaskUri().getValue()) + .GET() + .build(); + var response = client.send(request, HttpResponse.BodyHandlers.ofString()); + LOGGER.info(response.body()); + JSONObject responseBody = new JSONObject(response.body()); + + var task = new Task( + new Task.TaskName(responseBody.getString("taskName")), + new Task.TaskType(responseBody.getString("taskType")), + new Task.OriginalTaskUri(auction.get().getTaskUri().getValue().toString()), + new Task.TaskStatus(ch.unisg.tapas.auctionhouse.domain.Task.Status.ASSIGNED), + new Task.TaskId(responseBody.getString("taskId")), + new Task.InputData(responseBody.getString("inputData")), + new Task.ServiceProvider("TODO") + ); + + String body = TaskJsonRepresentation.serialize(task); + LOGGER.info(body); + var postURI = URI.create(auction.get().getAuctionHouseUri().getValue().toString() + "/taskwinner"); + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(postURI) + .header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + var postResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); + + LOGGER.info(postResponse.statusCode()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e){ + LOGGER.error(e.getMessage(), e); + } } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/BidReceivedHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/BidReceivedHandler.java new file mode 100644 index 0000000..dc992ac --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/handler/BidReceivedHandler.java @@ -0,0 +1,17 @@ +package ch.unisg.tapas.auctionhouse.application.handler; + +import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent; +import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEventHandler; +import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry; +import org.springframework.stereotype.Component; + +@Component +public class BidReceivedHandler implements BidReceivedEventHandler { + @Override + public boolean handleNewBidReceivedEvent(BidReceivedEvent bidReceivedEvent){ + var auction = AuctionRegistry.getInstance().getAuctionById(bidReceivedEvent.bid.getAuctionId()); + // TODO Handle if auction not there + auction.get().addBid(bidReceivedEvent.bid); + return true; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEvent.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEvent.java new file mode 100644 index 0000000..560f50b --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEvent.java @@ -0,0 +1,17 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +import ch.unisg.tapas.auctionhouse.domain.Bid; +import ch.unisg.tapas.common.SelfValidating; +import lombok.Getter; + +import javax.validation.constraints.NotNull; + +public class BidReceivedEvent extends SelfValidating { + @NotNull + public Bid bid; + + public BidReceivedEvent(Bid bid){ + this.bid = bid; + validateSelf(); + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEventHandler.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEventHandler.java new file mode 100644 index 0000000..b17ac6b --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/BidReceivedEventHandler.java @@ -0,0 +1,5 @@ +package ch.unisg.tapas.auctionhouse.application.port.in; + +public interface BidReceivedEventHandler { + boolean handleNewBidReceivedEvent(BidReceivedEvent bidReceivedEvent); +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Task.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Task.java new file mode 100644 index 0000000..3fd0d89 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Task.java @@ -0,0 +1,122 @@ +package ch.unisg.tapas.auctionhouse.domain; + +import lombok.Getter; +import lombok.Setter; +import lombok.Value; + +import java.util.UUID; + +/**This is a domain entity**/ +public class Task { + public enum Status { + OPEN, ASSIGNED, RUNNING, EXECUTED + } + + @Getter + private final TaskId taskId; + + @Getter + private final TaskName taskName; + + @Getter + private final TaskType taskType; + + @Getter @Setter + public TaskStatus taskStatus; // had to make public for CompleteTaskService + + @Getter + public TaskResult taskResult; // same as above + + @Getter + private final OriginalTaskUri originalTaskUri; + + @Getter @Setter + private ServiceProvider provider; + + @Getter @Setter + private InputData inputData; + + @Getter @Setter + private OutputData outputData; + + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) { + this.taskName = taskName; + this.taskType = taskType; + this.taskStatus = new TaskStatus(Status.OPEN); + this.taskId = new TaskId(UUID.randomUUID().toString()); + this.taskResult = new TaskResult(""); + this.originalTaskUri = taskUri; + + this.inputData = null; + this.outputData = null; + } + + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, TaskStatus taskStatus, TaskId taskId, InputData inputData, ServiceProvider serviceProvider) { + this.taskName = taskName; + this.taskType = taskType; + this.taskStatus = taskStatus; + this.taskId = taskId; + this.taskResult = new TaskResult(""); + this.originalTaskUri = taskUri; + this.provider = serviceProvider; + + this.inputData = inputData; + this.outputData = new OutputData(""); + } + + protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { + //This is a simple debug message to see that the request has reached the right method in the core + System.out.println("New Task: " + name.getValue() + " " + type.getValue()); + return new Task(name, type, null); + } + + protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, + OriginalTaskUri originalTaskUri) { + return new Task(name, type, originalTaskUri); + } + + @Value + public static class TaskId { + String value; + } + + @Value + public static class TaskName { + String value; + } + + @Value + public static class TaskType { + String value; + } + + @Value + public static class OriginalTaskUri { + String value; + } + + @Value + public static class TaskStatus { + Status value; + } + + @Value + public static class ServiceProvider { + String value; + } + + @Value + public static class InputData { + String value; + } + + @Value + public static class OutputData { + String value; + } + + @Value + public static class TaskResult{ + private String value; + } +} diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 706362e..7b94a5a 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -5,7 +5,7 @@ websub.hub.publish=https://websub.appspot.com/ group=tapas-group-tutors auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ -tasks.list.uri=https://tapas-tasks.86-119-34-23.nip.io/ +tasks.list.uri=http://localhost:8081 application.environment=development auctionhouse.uri=http://localhost:8086 -- 2.45.1 From e869fb96992ad47f9c8192dd5e5622487a571c5b Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 16 Nov 2021 18:51:39 +0100 Subject: [PATCH 74/94] Bidding workflow --- .../src/main/resources/application.properties | 2 +- .../tapas/TapasAuctionHouseApplication.java | 2 +- .../formats/TaskJsonRepresentation.java | 9 ++ .../mqtt/AuctionEventsMqttDispatcher.java | 1 + ...uctionStartedEventListenerMqttAdapter.java | 86 +++++++++++++++++++ .../in/web/WinningBidWebController.java | 59 +++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java diff --git a/executor-pool/src/main/resources/application.properties b/executor-pool/src/main/resources/application.properties index 0c9ba7e..c8fd60a 100644 --- a/executor-pool/src/main/resources/application.properties +++ b/executor-pool/src/main/resources/application.properties @@ -1,3 +1,3 @@ server.port=8083 -mqtt.broker.uri=tcp://localhost:1883 +mqtt.broker.uri=tcp://broker.hivemq.com:1883 diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 7438032..18c7631 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -27,7 +27,7 @@ public class TapasAuctionHouseApplication { private ConfigProperties config; public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; - public static String MQTT_BROKER = "tcp://localhost:1883"; + public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java index 467c550..782978c 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/formats/TaskJsonRepresentation.java @@ -112,4 +112,13 @@ final public class TaskJsonRepresentation { return mapper.writeValueAsString(representation); } + + public String serialize() throws JsonProcessingException { + TaskJsonRepresentation representation = this; + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return mapper.writeValueAsString(representation); + } } diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java index 7d30453..91872f2 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java @@ -28,6 +28,7 @@ public class AuctionEventsMqttDispatcher { private void initRouter() { router.put("ch/unisg/tapas/executors/added", new ExecutorAddedEventListenerMqttAdapter()); router.put("ch/unisg/tapas/executors/removed", new ExecutorRemovedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas/auctions", new ExternalAuctionStartedEventListenerMqttAdapter()); } /** diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java new file mode 100644 index 0000000..5e17d96 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java @@ -0,0 +1,86 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt; + +import ch.unisg.tapas.auctionhouse.adapter.common.formats.BidJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.TaskJsonRepresentation; +import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.Bid; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.beans.factory.annotation.Value; + +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.sql.Timestamp; + +public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEventMqttListener{ + private static final Logger LOGGER = LogManager.getLogger(ExternalAuctionStartedEventListenerMqttAdapter.class); + + @Value("${auction.house.uri}") + String auctionHouseURI; + + @Value("${tasks.list.uri}") + String taskListURI; + + @Override + public boolean handleEvent(MqttMessage message){ + String payload = new String(message.getPayload()); + + try { + // Note: this message representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String auctionId = data.get("auctionId").asText(); + String auctionHouseUri = data.get("auctionHouseUri").asText(); + String taskUri = data.get("taskUri").asText(); + String taskType = data.get("taskType").asText(); + String deadline = data.get("deadline").asText(); + + var capable = ExecutorRegistry.getInstance().containsTaskType(new Auction.AuctionedTaskType(taskType)); + // TODO check deadline + if(capable){ + var bid = new Bid( + new Auction.AuctionId(auctionId), + new Bid.BidderName("Group-1"), + new Bid.BidderAuctionHouseUri(URI.create(auctionHouseUri)), + new Bid.BidderTaskListUri(URI.create(taskListURI)) + ); + + String body = BidJsonRepresentation.serialize(bid); + LOGGER.info(body); + var postURI = URI.create(auctionHouseUri + "/bid"); + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(postURI) + .header("Content-Type", BidJsonRepresentation.MEDIA_TYPE) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + HttpClient client = HttpClient.newHttpClient(); + var postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString()); + + LOGGER.info(postResponse.statusCode()); + } + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e){ + LOGGER.error(e.getMessage(), e); + } + + return true; + } +} diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java new file mode 100644 index 0000000..9d252b5 --- /dev/null +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java @@ -0,0 +1,59 @@ +package ch.unisg.tapas.auctionhouse.adapter.in.web; + +import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.BidJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.common.formats.TaskJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.ExternalAuctionStartedEventListenerMqttAdapter; +import ch.unisg.tapas.auctionhouse.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +@RestController +public class WinningBidWebController { + private static final Logger LOGGER = LogManager.getLogger(WinningBidWebController.class); + + @Value("${tasks.list.uri}") + String taskListURI; + + @PostMapping(path = "/taskwinner", consumes = TaskJsonRepresentation.MEDIA_TYPE) + public ResponseEntity winningBid(@RequestBody TaskJsonRepresentation payload){ + try { + var body = payload.serialize(); + LOGGER.info(body); + var postURI = URI.create(taskListURI + "/tasks/"); + HttpRequest postRequest = HttpRequest.newBuilder() + .uri(postURI) + .header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + HttpClient client = HttpClient.newHttpClient(); + var postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString()); + + LOGGER.info(postResponse.statusCode()); + + + HttpHeaders responseHeaders = new HttpHeaders(); + return new ResponseEntity<>(responseHeaders, HttpStatus.NO_CONTENT); + } + catch ( + IOException | InterruptedException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } +} -- 2.45.1 From 5a2cc7a131fb5e07daa0c45155eedd4990fafb88 Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 16 Nov 2021 18:53:06 +0100 Subject: [PATCH 75/94] Changed mqtt broker address in roster --- roster/src/main/java/ch/unisg/roster/RosterApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roster/src/main/java/ch/unisg/roster/RosterApplication.java b/roster/src/main/java/ch/unisg/roster/RosterApplication.java index bc18c54..bc9ed86 100644 --- a/roster/src/main/java/ch/unisg/roster/RosterApplication.java +++ b/roster/src/main/java/ch/unisg/roster/RosterApplication.java @@ -16,7 +16,7 @@ public class RosterApplication { static Logger logger = Logger.getLogger(RosterApplication.class.getName()); - public static String MQTT_BROKER = "tcp://localhost:1883"; + public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; public static void main(String[] args) { SpringApplication.run(RosterApplication.class, args); -- 2.45.1 From 44cc0929bd2d578a6d9b4263253a6258d2a820d1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Nov 2021 19:09:38 +0100 Subject: [PATCH 76/94] fixes --- .../web/ExecutionFinishedEventAdapter.java | 9 +-- .../adapter/out/web/GetAssignmentAdapter.java | 5 +- .../domain/ExecutionFinishedEvent.java | 6 +- .../executor/domain/ExecutorBase.java | 8 +- .../executorBase/executor/domain/Task.java | 10 ++- .../executor/domain/Executor.java | 55 +++++++++----- .../executor/domain/Executor.java | 2 +- .../adapter/in/web/NewTaskController.java | 3 +- .../in/web/TaskCompletedController.java | 4 +- .../web/PublishTaskAssignedEventAdapter.java | 34 ++++----- .../web/PublishTaskCompletedEventAdapter.java | 13 +++- .../application/port/in/NewTaskCommand.java | 6 +- .../application/service/NewTaskService.java | 2 +- .../ch/unisg/roster/roster/domain/Task.java | 12 ++- .../AddNewTaskToTaskListWebController.java | 11 ++- .../in/web/CompleteTaskWebController.java | 14 +++- .../web/ExternalTaskExecutedWebAdapter.java | 74 +++++++++++++++++++ .../PublishNewTaskAddedEventWebAdapter.java | 1 + .../port/in/AddNewTaskToTaskListCommand.java | 12 ++- .../port/in/CompleteTaskCommand.java | 7 +- .../port/out/ExternalTaskExecutedEvent.java | 28 +++++++ .../out/ExternalTaskExecutedEventHandler.java | 5 ++ .../service/AddNewTaskToTaskListService.java | 35 +++++++-- .../service/CompleteTaskService.java | 16 +++- .../tasks/domain/NewTaskAddedEvent.java | 4 +- .../unisg/tapastasks/tasks/domain/Task.java | 17 +++++ .../tapastasks/tasks/domain/TaskList.java | 9 +++ 27 files changed, 307 insertions(+), 95 deletions(-) create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEvent.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEventHandler.java diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 58f6287..e618c79 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -29,12 +29,9 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort @Override public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { - System.out.println("HI"); - System.out.println(server); - String body = new JSONObject() .put("taskID", event.getTaskID()) - .put("result", event.getResult()) + .put("outputData", event.getOutputData()) .put("status", event.getStatus()) .toString(); @@ -46,8 +43,6 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort .build(); - System.out.println(server); - try { client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (InterruptedException e) { @@ -57,7 +52,7 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } - logger.log(Level.INFO, "Finish execution event sent with result: {0}", event.getResult()); + logger.log(Level.INFO, "Finish execution event sent with result: {0}", event.getOutputData()); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index dd82c81..92cea92 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -58,9 +58,8 @@ public class GetAssignmentAdapter implements GetAssignmentPort { } JSONObject responseBody = new JSONObject(response.body()); - String[] input = { "1", "+", "2" }; - // TODO Add input in roster + tasklist - return new Task(responseBody.getString("taskID"), input); + String inputData = responseBody.getString("inputData"); + return new Task(responseBody.getString("taskID"), inputData); } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java index fea6102..56637c4 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java @@ -8,14 +8,14 @@ public class ExecutionFinishedEvent { private String taskID; @Getter - private String result; + private String outputData; @Getter private String status; - public ExecutionFinishedEvent(String taskID, String result, String status) { + public ExecutionFinishedEvent(String taskID, String outputData, String status) { this.taskID = taskID; - this.result = result; + this.outputData = outputData; this.status = status; } } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index b8e8631..14582e7 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -71,13 +71,11 @@ public abstract class ExecutorBase { logger.info("Starting execution"); this.status = ExecutorStatus.EXECUTING; - task.setResult(execution(task.getInput())); - - System.out.println(task.getResult()); + task.setOutputData(execution(task.getInputData())); // TODO implement logic if execution was not successful executionFinishedEventPort.publishExecutionFinishedEvent( - new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); + new ExecutionFinishedEvent(task.getTaskID(), task.getOutputData(), "SUCCESS")); logger.info("Finish execution"); getAssignment(); @@ -87,6 +85,6 @@ public abstract class ExecutorBase { * Implementation of the actual execution method of an executor * @return the execution result **/ - protected abstract String execution(String... input); + protected abstract String execution(String input); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java index 7dc5783..44595e1 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java @@ -10,14 +10,16 @@ public class Task { @Getter @Setter - private String result; + private String outputData; + // TODO maybe create a value object for inputData so we can make sure it is in the right + // format. @Getter - private String[] input; + private String inputData; - public Task(String taskID, String... input) { + public Task(String taskID, String inputData) { this.taskID = taskID; - this.input = input; + this.inputData= inputData; } } diff --git a/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java index c3fbb67..532099b 100644 --- a/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java +++ b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java @@ -19,30 +19,45 @@ public class Executor extends ExecutorBase { @Override protected - String execution(String... input) { + String execution(String inputData) { - System.out.println(input); + System.out.println(inputData); - double result = Double.NaN; - int a = Integer.parseInt(input[0]); - int b = Integer.parseInt(input[2]); - String operation = input[1]; - - // try { - // TimeUnit.SECONDS.sleep(20); - // } catch (InterruptedException e) { - // e.printStackTrace(); - // } - - if (operation == "+") { - result = a + b; - } else if (operation == "*") { - result = a * b; - } else if (operation == "-") { - result = a - b; + String operator = ""; + if (inputData.contains("+")) { + operator = "+"; + } else if (inputData.contains("-")) { + operator = "-"; + } else if (inputData.contains("*")) { + operator = "*"; } - System.out.println("finish"); + // System.out.println(operator); + + // double result = Double.NaN; + + // System.out.print(inputData.split("+")); + + // int a = Integer.parseInt(inputData.split(operator)[0]); + // int b = Integer.parseInt(inputData.split(operator)[1]); + + // // try { + // // TimeUnit.SECONDS.sleep(20); + // // } catch (InterruptedException e) { + // // e.printStackTrace(); + // // } + + // if (operator.equalsIgnoreCase("+")) { + // result = a + b; + // } else if (operator.equalsIgnoreCase("*")) { + // result = a * b; + // } else if (operator.equalsIgnoreCase("-")) { + // result = a - b; + // } + + // System.out.println("Result: " + result); + + double result = 0.0; return Double.toString(result); } diff --git a/executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java index 4124e9e..e83579c 100644 --- a/executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java +++ b/executor-robot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java @@ -28,7 +28,7 @@ public class Executor extends ExecutorBase { @Override protected - String execution(String... input) { + String execution(String input) { String key = userToRobotPort.userToRobot(); try { diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java index 98b3ac7..7ff5349 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java @@ -31,7 +31,8 @@ public class NewTaskController { logger.info("New task with id:" + task.getTaskID()); - NewTaskCommand command = new NewTaskCommand(task.getTaskID(), task.getTaskType()); + NewTaskCommand command = new NewTaskCommand(task.getTaskID(), task.getTaskType(), + task.getInputData()); boolean success = newTaskUseCase.addNewTaskToQueue(command); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java index f81db32..5adfd7e 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java @@ -25,9 +25,9 @@ public class TaskCompletedController { **/ @PostMapping(path = "/task/completed", consumes = {"application/json"}) public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { - + System.out.println("TEST"); TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), - task.getStatus(), task.getResult()); + task.getStatus(), task.getOutputData()); taskCompletedUseCase.taskCompleted(command); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java index c71e306..2c75a03 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -32,26 +32,26 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { @Override public void publishTaskAssignedEvent(TaskAssignedEvent event) { - String body = new JSONObject() - .put("taskId", event.taskID) - .toString(); + // String body = new JSONObject() + // .put("taskId", event.taskID) + // .toString(); - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/tasks/assignTask")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(body)) - .build(); + // HttpClient client = HttpClient.newHttpClient(); + // HttpRequest request = HttpRequest.newBuilder() + // .uri(URI.create(server + "/tasks/assignTask")) + // .header("Content-Type", "application/task+json") + // .POST(HttpRequest.BodyPublishers.ofString(body)) + // .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); - } + // 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); + // } } } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java index 7038291..3773621 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -32,17 +32,22 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort @Override public void publishTaskCompleted(TaskCompletedEvent event) { + System.out.println("PublishTaskCompletedEventAdapter.publishTaskCompleted()"); + System.out.print(server); + String body = new JSONObject() .put("taskId", event.taskID) .put("status", event.status) - .put("taskResult", event.result) + .put("outputData", event.result) .toString(); + System.out.println(event.taskID); + HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/tasks/completeTask")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(body)) + .uri(URI.create(server + "/tasks/completeTask/" + event.taskID)) + .header("Content-Type", "application/task+json") + .GET() .build(); diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java index 92a7403..5db2b9f 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java @@ -17,9 +17,13 @@ public class NewTaskCommand extends SelfValidating { @NotNull private final ExecutorType taskType; - public NewTaskCommand(String taskID, ExecutorType taskType) { + @NotNull + private final String inputData; + + public NewTaskCommand(String taskID, ExecutorType taskType, String inputData) { this.taskID = taskID; this.taskType = taskType; + this.inputData = inputData; this.validateSelf(); } } diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java index c6e1685..c1aab5c 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java @@ -34,7 +34,7 @@ public class NewTaskService implements NewTaskUseCase { return false; } - Task task = new Task(command.getTaskID(), command.getTaskType()); + Task task = new Task(command.getTaskID(), command.getTaskType(), command.getInputData()); Roster.getInstance().addTaskToQueue(task); diff --git a/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java b/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java index 40ef9fa..ee30763 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java @@ -14,7 +14,11 @@ public class Task { @Getter @Setter - private String result; + private String inputData; + + @Getter + @Setter + private String outputData; @Getter @Setter @@ -30,6 +34,12 @@ public class Task { this.taskType = taskType; } + public Task(String taskID, ExecutorType taskType, String inputData) { + this.taskID = taskID; + this.taskType = taskType; + this.inputData = inputData; + } + public Task() {} } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java index 234dcde..15c3ebb 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java @@ -55,16 +55,15 @@ public class AddNewTaskToTaskListWebController { (payload.getOriginalTaskUri() == null) ? Optional.empty() : Optional.of(new Task.OriginalTaskUri(payload.getOriginalTaskUri())); + Optional inputData = + (payload.getInputData() == null) ? Optional.empty() + : Optional.of(new Task.InputData(payload.getInputData())); + AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand(taskName, taskType, - originalTaskUriOptional); + originalTaskUriOptional, inputData); Task createdTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command); - // When creating a task, the task's representation may include optional input data - if (payload.getInputData() != null) { - createdTask.setInputData(new Task.InputData(payload.getInputData())); - } - // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java index ec2b7b0..fa5578b 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java @@ -8,8 +8,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; 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.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; @@ -23,12 +26,17 @@ public class CompleteTaskWebController { this.completeTaskUseCase = completeTaskUseCase; } - @PostMapping(path = "/tasks/completeTask", consumes = {TaskJsonRepresentation.MEDIA_TYPE}) - public ResponseEntity completeTask (@RequestBody Task task){ + @GetMapping(path = "/tasks/completeTask/{taskId}") + public ResponseEntity completeTask (@PathVariable("taskId") String taskId){ + + System.out.println("completeTask"); + System.out.println(taskId); + + String taskResult = "0"; try { CompleteTaskCommand command = new CompleteTaskCommand( - task.getTaskId(), task.getTaskResult() + new Task.TaskId(taskId), new Task.OutputData(taskResult) ); Task updateATask = completeTaskUseCase.completeTask(command); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java new file mode 100644 index 0000000..8e6f106 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java @@ -0,0 +1,74 @@ +package ch.unisg.tapastasks.tasks.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 com.github.fge.jsonpatch.JsonPatch; +import com.github.fge.jsonpatch.JsonPatchOperation; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation; +import ch.unisg.tapastasks.tasks.application.port.out.ExternalTaskExecutedEvent; +import ch.unisg.tapastasks.tasks.application.port.out.ExternalTaskExecutedEventHandler; + +@Component +@Primary +public class ExternalTaskExecutedWebAdapter implements ExternalTaskExecutedEventHandler { + + Logger logger = Logger.getLogger(ExternalTaskExecutedWebAdapter.class.getName()); + + /** + * Updates an external task which got executed in our system. + **/ + @Override + public void handleEvent(ExternalTaskExecutedEvent externalTaskExecutedEvent) { + + JSONObject op1; + JSONObject op2; + try { + op1 = new JSONObject() + .put("op", "replace") + .put("path", "/taskStatus") + .put("value", "EXECUTED"); + + op2 = new JSONObject() + .put("op", "add") + .put("path", "/outputData") + .put("value", "0"); + } catch (JSONException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); + return; + } + + String body = new JSONArray().put(op1).put(op2).toString(); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(externalTaskExecutedEvent.getOriginalTaskUri().getValue())) + .header("Content-Type", "application/json") + .method("PATCH", HttpRequest.BodyPublishers.ofString(body)) + .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); + } + + } + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java index 569b1e9..80b3d09 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java @@ -29,6 +29,7 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort var values = new HashMap() {{ put("taskID", event.taskId); put("taskType", event.taskType); + put("inputData", event.inputData); }}; var objectMapper = new ObjectMapper(); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java index fbb66ed..d307a1f 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/AddNewTaskToTaskListCommand.java @@ -19,11 +19,19 @@ public class AddNewTaskToTaskListCommand extends SelfValidating originalTaskUri; - public AddNewTaskToTaskListCommand(Task.TaskName taskName, Task.TaskType taskType, - Optional originalTaskUri) { + @Getter + private final Optional inputData; + + public AddNewTaskToTaskListCommand( + Task.TaskName taskName, + Task.TaskType taskType, + Optional originalTaskUri, + Optional inputData + ) { this.taskName = taskName; this.taskType = taskType; this.originalTaskUri = originalTaskUri; + this.inputData = inputData; this.validateSelf(); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java index 0634165..238abd2 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/CompleteTaskCommand.java @@ -1,6 +1,7 @@ package ch.unisg.tapastasks.tasks.application.port.in; import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task.OutputData; import ch.unisg.tapastasks.tasks.domain.Task.TaskId; import ch.unisg.tapastasks.tasks.domain.Task.TaskResult; import lombok.Value; @@ -13,11 +14,11 @@ public class CompleteTaskCommand extends SelfValidating { private final TaskId taskId; @NotNull - private final TaskResult taskResult; + private final OutputData outputData; - public CompleteTaskCommand(TaskId taskId, TaskResult taskResult){ + public CompleteTaskCommand(TaskId taskId, OutputData outputData){ this.taskId = taskId; - this.taskResult = taskResult; + this.outputData = outputData; this.validateSelf(); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEvent.java new file mode 100644 index 0000000..43bad47 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEvent.java @@ -0,0 +1,28 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import javax.validation.constraints.NotNull; + +import ch.unisg.tapastasks.common.SelfValidating; +import ch.unisg.tapastasks.tasks.domain.Task; +import lombok.Getter; +import lombok.Value; + +@Value +public class ExternalTaskExecutedEvent extends SelfValidating { + @NotNull + private final Task.TaskId taskId; + + @Getter + private final Task.OriginalTaskUri originalTaskUri; + + @Getter + private final Task.OutputData outputData; + + public ExternalTaskExecutedEvent(Task.TaskId taskId, Task.OriginalTaskUri originalTaskUri, Task.OutputData outputData) { + this.taskId = taskId; + this.originalTaskUri = originalTaskUri; + this.outputData = outputData; + + this.validateSelf(); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEventHandler.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEventHandler.java new file mode 100644 index 0000000..90bff49 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/ExternalTaskExecutedEventHandler.java @@ -0,0 +1,5 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +public interface ExternalTaskExecutedEventHandler { + void handleEvent(ExternalTaskExecutedEvent externalTaskExecutedEvent); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 70818b1..26234ce 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -22,12 +22,26 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) { TaskList taskList = TaskList.getTapasTaskList(); - Task newTask = (command.getOriginalTaskUri().isPresent()) ? - // Create a delegated task that points back to the original task - taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), - command.getTaskType(), command.getOriginalTaskUri().get()) - // Create an original task - : taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); + Task newTask; + + System.out.println("TEST:"); + System.out.println(command.getInputData().get()); + + if (command.getOriginalTaskUri().isPresent() && command.getInputData().isPresent()) { + System.out.println("TEST2:"); + newTask = taskList.addNewTaskWithNameAndTypeAndOriginalTaskUriAndInputData(command.getTaskName(), + command.getTaskType(), command.getOriginalTaskUri().get(), command.getInputData().get()); + } else if (command.getOriginalTaskUri().isPresent()) { + newTask = taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), + command.getTaskType(), command.getOriginalTaskUri().get()); + } else if (command.getOriginalTaskUri().isPresent()) { + newTask = null; + } else { + newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); + } + + System.out.println("TEST"); + System.out.println(newTask.getInputData()); //Here we are using the application service to emit the domain event to the outside of the bounded context. //This event should be considered as a light-weight "integration event" to communicate with other services. @@ -35,8 +49,13 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase //not recommended to emit a domain event via an application service! You should first emit the domain event in //the core and then the integration event in the application layer. if (newTask != null) { - NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent(newTask.getTaskName().getValue(), - taskList.getTaskListName().getValue(), newTask.getTaskId().getValue(), newTask.getTaskType().getValue()); + NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent( + newTask.getTaskName().getValue(), + taskList.getTaskListName().getValue(), + newTask.getTaskId().getValue(), + newTask.getTaskType().getValue(), + newTask.getInputData().getValue() + ); newTaskAddedEventPort.publishNewTaskAddedEvent(newTaskAdded); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java index 0e7f817..df22421 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java @@ -2,10 +2,11 @@ package ch.unisg.tapastasks.tasks.application.service; import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskCommand; import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskUseCase; +import ch.unisg.tapastasks.tasks.application.port.out.ExternalTaskExecutedEvent; +import ch.unisg.tapastasks.tasks.application.port.out.ExternalTaskExecutedEventHandler; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.Task.*; import ch.unisg.tapastasks.tasks.domain.TaskList; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -17,15 +18,26 @@ import java.util.Optional; @Transactional public class CompleteTaskService implements CompleteTaskUseCase { + private final ExternalTaskExecutedEventHandler externalTaskExecutedEventHandler; + @Override public Task completeTask(CompleteTaskCommand command){ TaskList taskList = TaskList.getTapasTaskList(); Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); Task newTask = updatedTask.get(); - newTask.taskResult = new TaskResult(command.getTaskResult().getValue()); + newTask.taskResult = new TaskResult(command.getOutputData().getValue()); newTask.taskStatus = new TaskStatus(Task.Status.EXECUTED); + if (!newTask.getOriginalTaskUri().getValue().equalsIgnoreCase("")) { + ExternalTaskExecutedEvent event = new ExternalTaskExecutedEvent( + newTask.getTaskId(), + newTask.getOriginalTaskUri(), + newTask.getOutputData() + ); + externalTaskExecutedEventHandler.handleEvent(event); + } + return newTask; } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java index a4703f2..049c2fb 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/NewTaskAddedEvent.java @@ -6,12 +6,14 @@ public class NewTaskAddedEvent { public String taskListName; public String taskId; public String taskType; + public String inputData; - public NewTaskAddedEvent(String taskName, String taskListName, String taskId, String taskType) { + public NewTaskAddedEvent(String taskName, String taskListName, String taskId, String taskType, String inputData) { this.taskName = taskName; this.taskListName = taskListName; this.taskId = taskId; this.taskType = taskType; + this.inputData = inputData; } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index 4893045..d07f0f1 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -51,6 +51,18 @@ public class Task { this.outputData = null; } + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, InputData inputData) { + this.taskName = taskName; + this.taskType = taskType; + this.taskStatus = new TaskStatus(Status.OPEN); + this.taskId = new TaskId(UUID.randomUUID().toString()); + this.taskResult = new TaskResult(""); + this.originalTaskUri = taskUri; + + this.inputData = inputData; + this.outputData = null; + } + protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { //This is a simple debug message to see that the request has reached the right method in the core System.out.println("New Task: " + name.getValue() + " " + type.getValue()); @@ -62,6 +74,11 @@ public class Task { return new Task(name, type, originalTaskUri); } + protected static Task createTaskWithNameAndTypeAndOriginalTaskUriAndInputData(TaskName name, TaskType type, + OriginalTaskUri originalTaskUri, InputData inputData) { + return new Task(name, type, originalTaskUri, inputData); + } + @Value public static class TaskId { String value; diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java index d7bb877..72160e8 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java @@ -48,6 +48,15 @@ public class TaskList { return newTask; } + public Task addNewTaskWithNameAndTypeAndOriginalTaskUriAndInputData(Task.TaskName name, Task.TaskType type, + Task.OriginalTaskUri originalTaskUri, Task.InputData inputData) { + Task newTask = Task.createTaskWithNameAndTypeAndOriginalTaskUriAndInputData(name, type, originalTaskUri, inputData); + this.addNewTaskToList(newTask); + + return newTask; + } + + private void addNewTaskToList(Task newTask) { //Here we would also publish a domain event to other entities in the core interested in this event. //However, we skip this here as it makes the core even more complex (e.g., we have to implement a light-weight -- 2.45.1 From 1bc6ec6813b98eda8c2b6bf9c6900caeba30c48a Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 16 Nov 2021 19:41:56 +0100 Subject: [PATCH 77/94] Small URI fixes --- .../ExternalAuctionStartedEventListenerMqttAdapter.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java index 5e17d96..13374cc 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java @@ -25,11 +25,9 @@ import java.sql.Timestamp; public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEventMqttListener{ private static final Logger LOGGER = LogManager.getLogger(ExternalAuctionStartedEventListenerMqttAdapter.class); - @Value("${auction.house.uri}") - String auctionHouseURI; + String auctionHouseURI = "http://tapas-auction-house.86-119-35-40.nip.io/"; - @Value("${tasks.list.uri}") - String taskListURI; + String taskListURI = "http://tapas-tasks.86-119-35-40.nip.io"; @Override public boolean handleEvent(MqttMessage message){ @@ -52,7 +50,7 @@ public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEvent var bid = new Bid( new Auction.AuctionId(auctionId), new Bid.BidderName("Group-1"), - new Bid.BidderAuctionHouseUri(URI.create(auctionHouseUri)), + new Bid.BidderAuctionHouseUri(URI.create(auctionHouseURI)), new Bid.BidderTaskListUri(URI.create(taskListURI)) ); -- 2.45.1 From 084270bbb8e0fd9270dc525a0c896ec711187e59 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Nov 2021 19:43:46 +0100 Subject: [PATCH 78/94] env changes --- tapas-auction-house/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 7b94a5a..7f8ca02 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -4,7 +4,7 @@ websub.hub=https://websub.appspot.com/ websub.hub.publish=https://websub.appspot.com/ group=tapas-group-tutors -auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ +auction.house.uri=http://tapas-auction-house.86-119-35-40.nip.io tasks.list.uri=http://localhost:8081 application.environment=development -- 2.45.1 From b6488fa36f2036d717388b42494f13c703e63336 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Nov 2021 21:17:10 +0100 Subject: [PATCH 79/94] added environment variables for uri's --- .deployment/docker-compose.yml | 7 ++++++- .../adapter/out/web/ExecutionFinishedEventAdapter.java | 7 +++---- .../executor/adapter/out/web/GetAssignmentAdapter.java | 5 ++--- .../adapter/out/web/NotifyExecutorPoolAdapter.java | 7 ++++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 5a1329f..3fe4c2a 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -95,6 +95,9 @@ services: - roster volumes: - ./:/data/ + environment: + - executor_pool_uri: "executor-pool.86-119-35-40.nip.io" + - roster_uri: "roster.86-119-35-40.nip.io" labels: - "traefik.enable=true" - "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)" @@ -103,7 +106,6 @@ services: - "traefik.http.routers.executor-computation.tls=true" - "traefik.http.routers.executor-computation.entryPoints=web,websecure" - "traefik.http.routers.executor-computation.tls.certresolver=le" - executor-robot: image: openjdk command: "java -jar /data/executor-robot-0.0.1-SNAPSHOT.jar" @@ -113,6 +115,9 @@ services: - roster volumes: - ./:/data/ + environment: + - executor_pool_uri: "executor-pool.86-119-35-40.nip.io" + - roster_uri: "roster.86-119-35-40.nip.io" labels: - "traefik.enable=true" - "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)" diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index e618c79..4321f72 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -9,16 +9,15 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Value; import ch.unisg.executorbase.executor.application.port.out.ExecutionFinishedEventPort; import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { - // TODO url doesn't get mapped bc no autowiring - @Value("${roster.url}") - String server = "http://localhost:8082"; + String server = System.getenv("roster_uri") == null ? + "http://localhost:8082" : System.getenv("roster_uri"); + Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName()); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index 92cea92..9d8013b 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -23,9 +23,8 @@ import org.json.JSONObject; @Primary public class GetAssignmentAdapter implements GetAssignmentPort { - // TODO Not working for now bc it doesn't get autowired - @Value("${roster.url}") - String server = "http://127.0.0.1:8082"; + String server = System.getenv("roster_uri") == null ? + "http://localhost:8082" : System.getenv("roster_uri"); Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName()); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index abc0cf5..ebb6fc6 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -22,9 +22,8 @@ import ch.unisg.executorbase.executor.domain.ExecutorType; @Primary public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { - // TODO Not working for now bc it doesn't get autowired - @Value("${executor.pool.url}") - String server = "http://127.0.0.1:8083"; + String server = System.getenv("executor_pool_uri") == null ? + "http://localhost:8083" : System.getenv("executor_pool_uri"); Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); @@ -35,6 +34,8 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { @Override public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) { + System.out.println(server); + String body = new JSONObject() .put("executorTaskType", executorType) .put("executorUri", executorURI.getValue()) -- 2.45.1 From add257fd5e08d734a648902d250296222865398e Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Nov 2021 21:20:26 +0100 Subject: [PATCH 80/94] updated dependencies to remove security issues --- tapas-auction-house/pom.xml | 2 +- tapas-tasks/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tapas-auction-house/pom.xml b/tapas-auction-house/pom.xml index df44681..dd29302 100644 --- a/tapas-auction-house/pom.xml +++ b/tapas-auction-house/pom.xml @@ -46,7 +46,7 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.2.0 + 1.2.5 javax.transaction diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 39e1e67..715b947 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -75,7 +75,7 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.2.0 + 1.2.5 -- 2.45.1 From 292d30d1bdd4b1981f7e98333c9096cb80eab3aa Mon Sep 17 00:00:00 2001 From: Andrei Ciortea Date: Tue, 16 Nov 2021 22:41:22 +0100 Subject: [PATCH 81/94] Add example for reading config properties in the main method for TapasAuctionHouse --- .../tapas/TapasAuctionHouseApplication.java | 17 ++++++++++++++--- .../src/main/resources/application.properties | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 8fc22d0..e21e82d 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -9,6 +9,8 @@ import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import java.net.URI; import java.util.List; @@ -23,14 +25,15 @@ public class TapasAuctionHouseApplication { public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + private static ConfigurableEnvironment ENVIRONMENT; + public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); + ENVIRONMENT = tapasAuctioneerApp.run(args).getEnvironment(); // We will use these bootstrap methods in Week 6: // bootstrapMarketplaceWithWebSub(); // bootstrapMarketplaceWithMqtt(); - - tapasAuctioneerApp.run(args); } /** @@ -53,8 +56,16 @@ public class TapasAuctionHouseApplication { */ private static void bootstrapMarketplaceWithMqtt() { try { + String broker = ENVIRONMENT.getProperty("broker.mqtt"); + + if (broker == null) { + broker = MQTT_BROKER; + LOGGER.info("No MQTT broker was set in application.propreties, going with default: " + + MQTT_BROKER); + } + AuctionEventsMqttDispatcher dispatcher = new AuctionEventsMqttDispatcher(); - TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + TapasMqttClient client = TapasMqttClient.getInstance(broker, dispatcher); client.startReceivingMessages(); } catch (MqttException e) { LOGGER.error(e.getMessage(), e); diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 2c92c87..f69e5dd 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -1,5 +1,7 @@ server.port=8082 +broker.mqtt=tcp://broker.hivemq.com + websub.hub=https://websub.appspot.com/ websub.hub.publish=https://websub.appspot.com/ -- 2.45.1 From 01b5056671f8b0690b533ecebe7b696b9b80f523 Mon Sep 17 00:00:00 2001 From: reynisson Date: Wed, 17 Nov 2021 18:25:49 +0100 Subject: [PATCH 82/94] Testing out getting Environment variables from main --- .../tapas/TapasAuctionHouseApplication.java | 24 ++++++++++++------- ...uctionStartedEventListenerMqttAdapter.java | 5 ++++ .../src/main/resources/application.properties | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 18c7631..9fe963e 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.ConfigurableEnvironment; import java.net.URI; import java.util.List; @@ -23,20 +24,19 @@ import java.util.List; public class TapasAuctionHouseApplication { private static final Logger LOGGER = LogManager.getLogger(TapasAuctionHouseApplication.class); - @Autowired - private ConfigProperties config; - public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; - public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + public static String DEFAULT_MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + + private static ConfigurableEnvironment ENVIRONMENT; public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); + ENVIRONMENT = tapasAuctioneerApp.run(args).getEnvironment(); + // TODO Set start up of message services with config // We will use these bootstrap methods in Week 6: - bootstrapMarketplaceWithWebSub(); + // bootstrapMarketplaceWithWebSub(); bootstrapMarketplaceWithMqtt(); - - tapasAuctioneerApp.run(args); } /** * Discovers auction houses and subscribes to WebSub notifications @@ -57,8 +57,16 @@ public class TapasAuctionHouseApplication { */ private static void bootstrapMarketplaceWithMqtt() { try { + String broker = ENVIRONMENT.getProperty("mqtt.broker.uri"); + + if (broker == null) { + broker = DEFAULT_MQTT_BROKER; + LOGGER.info("No MQTT broker was set in application.propreties, going with default: " + + DEFAULT_MQTT_BROKER); + } + AuctionEventsMqttDispatcher dispatcher = new AuctionEventsMqttDispatcher(); - TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + TapasMqttClient client = TapasMqttClient.getInstance(broker, dispatcher); client.startReceivingMessages(); } catch (MqttException e) { LOGGER.error(e.getMessage(), e); diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java index 13374cc..c47acad 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java @@ -38,6 +38,7 @@ public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEvent // representation that makes sense in the context of your application. JsonNode data = new ObjectMapper().readTree(payload); + // TODO Sanitize URIs String auctionId = data.get("auctionId").asText(); String auctionHouseUri = data.get("auctionHouseUri").asText(); String taskUri = data.get("taskUri").asText(); @@ -72,8 +73,12 @@ public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEvent LOGGER.error(e.getMessage(), e); return false; } catch (IOException e) { + + LOGGER.error(e.getMessage(), e); e.printStackTrace(); } catch (InterruptedException e) { + + LOGGER.error(e.getMessage(), e); e.printStackTrace(); } catch (Exception e){ LOGGER.error(e.getMessage(), e); diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 7f8ca02..e3900bd 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -10,4 +10,4 @@ tasks.list.uri=http://localhost:8081 application.environment=development auctionhouse.uri=http://localhost:8086 websub.hub.uri=http://localhost:3000 -mqtt.broker.uri=tcp://localhost:1883 +mqtt.broker.uri=tcp://broker.hivemq.com:1883 -- 2.45.1 From 9d8e6cf2160c06eeccde80a25930b08df1979ed6 Mon Sep 17 00:00:00 2001 From: reynisson Date: Wed, 17 Nov 2021 18:29:08 +0100 Subject: [PATCH 83/94] Logging bids received --- .../auctionhouse/adapter/in/web/AddBidWebController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java index 3431c8d..41e65ff 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java @@ -2,10 +2,13 @@ package ch.unisg.tapas.auctionhouse.adapter.in.web; import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; import ch.unisg.tapas.auctionhouse.adapter.common.formats.BidJsonRepresentation; +import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.BidReceivedEventListenerMqttAdapter; import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler; import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent; import ch.unisg.tapas.auctionhouse.domain.Auction; import ch.unisg.tapas.auctionhouse.domain.Bid; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,6 +21,8 @@ import java.net.URI; // TODO Fix structure due to MQTT @RestController public class AddBidWebController { + private static final Logger LOGGER = LogManager.getLogger(AddBidWebController.class); + @PostMapping(path = "/bid", consumes = BidJsonRepresentation.MEDIA_TYPE) public ResponseEntity addBid(@RequestBody BidJsonRepresentation payload) { BidReceivedEvent bidReceivedEvent = new BidReceivedEvent(new Bid( @@ -27,6 +32,8 @@ public class AddBidWebController { new Bid.BidderTaskListUri(URI.create(payload.getBidderTaskListUri())) )); + LOGGER.info("Bid received", payload); + BidReceivedHandler bidReceivedHandler = new BidReceivedHandler(); bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent); -- 2.45.1 From 6b8f5bf013899cf6342918acce33f3f6f0b7e765 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 17 Nov 2021 19:00:52 +0100 Subject: [PATCH 84/94] fixed deployment script --- .deployment/docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 3fe4c2a..333820a 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -96,8 +96,8 @@ services: volumes: - ./:/data/ environment: - - executor_pool_uri: "executor-pool.86-119-35-40.nip.io" - - roster_uri: "roster.86-119-35-40.nip.io" + executor_pool_uri: executor-pool.86-119-35-40.nip.io + roster_uri: roster.86-119-35-40.nip.io" labels: - "traefik.enable=true" - "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)" @@ -116,8 +116,8 @@ services: volumes: - ./:/data/ environment: - - executor_pool_uri: "executor-pool.86-119-35-40.nip.io" - - roster_uri: "roster.86-119-35-40.nip.io" + executor_pool_uri: executor-pool.86-119-35-40.nip.io + roster_uri: roster.86-119-35-40.nip.io labels: - "traefik.enable=true" - "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)" -- 2.45.1 From 9d75a87dd6d0d72c988cec92cdb1768a474a64de Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 17 Nov 2021 19:54:04 +0100 Subject: [PATCH 85/94] deployment script & tasklist & executor fixes --- .deployment/docker-compose.yml | 10 ++++++---- .../adapter/out/web/NotifyExecutorPoolAdapter.java | 2 -- .../executorBase/executor/domain/ExecutorBase.java | 3 +++ .../adapter/out/web/CanTaskBeDeletedWebAdapter.java | 7 ++++--- .../out/web/PublishNewTaskAddedEventWebAdapter.java | 6 ++++-- tapas-tasks/src/main/resources/application.properties | 1 + 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 333820a..1dab865 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -32,6 +32,8 @@ services: restart: unless-stopped volumes: - ./:/data/ + environment: + roster.uri: http://roster:8082 labels: - "traefik.enable=true" - "traefik.http.routers.tapas-tasks.rule=Host(`tapas-tasks.${PUB_IP}.nip.io`)" @@ -96,8 +98,8 @@ services: volumes: - ./:/data/ environment: - executor_pool_uri: executor-pool.86-119-35-40.nip.io - roster_uri: roster.86-119-35-40.nip.io" + executor_pool_uri: http://executor-pool:8083 + roster_uri: http://roster:8082 labels: - "traefik.enable=true" - "traefik.http.routers.executor-computation.rule=Host(`executor-computation.${PUB_IP}.nip.io`)" @@ -116,8 +118,8 @@ services: volumes: - ./:/data/ environment: - executor_pool_uri: executor-pool.86-119-35-40.nip.io - roster_uri: roster.86-119-35-40.nip.io + executor_pool_uri: http://executor-pool:8083 + roster_uri: http://roster:8082 labels: - "traefik.enable=true" - "traefik.http.routers.executor-robot.rule=Host(`executor-robot.${PUB_IP}.nip.io`)" diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index ebb6fc6..bb38e66 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -34,8 +34,6 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { @Override public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) { - System.out.println(server); - String body = new JSONObject() .put("executorTaskType", executorType) .put("executorUri", executorURI.getValue()) diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index 14582e7..9f70dd1 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -1,5 +1,6 @@ package ch.unisg.executorbase.executor.domain; +import java.util.logging.Level; import java.util.logging.Logger; import ch.unisg.common.valueobject.ExecutorURI; @@ -41,8 +42,10 @@ public abstract class ExecutorBase { // 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(); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java index 5061e3d..a69f2e5 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java @@ -5,6 +5,8 @@ import ch.unisg.tapastasks.tasks.application.port.out.CanTaskBeDeletedPort; import ch.unisg.tapastasks.tasks.domain.DeleteTaskEvent; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -19,9 +21,8 @@ import java.util.HashMap; @Primary public class CanTaskBeDeletedWebAdapter implements CanTaskBeDeletedPort { - // Base URI of the service interested in this event - //Todo: Add the right IP address - String server = null; + @Value("${roster.uri}") + String server; @Override public void canTaskBeDeletedEvent(DeleteTaskEvent event){ diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java index 80b3d09..53435d0 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java @@ -4,6 +4,8 @@ import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -18,8 +20,8 @@ import java.util.HashMap; @Primary public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort { - //This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application) - String server = "http://127.0.0.1:8082"; + @Value("${roster.uri}") + String server; @Override public void publishNewTaskAddedEvent(NewTaskAddedEvent event) { diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index fe25873..485e4e7 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1,2 +1,3 @@ server.port=8081 baseuri=https://tapas-tasks.86-119-34-23.nip.io/ +roster.uri=http://127.0.0.1:8082 -- 2.45.1 From 6fe4b4dfbed9ed53caf0b893c4ff2f74fe746dad Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 17 Nov 2021 21:35:38 +0100 Subject: [PATCH 86/94] bugfixes + env variables --- .deployment/docker-compose.yml | 9 +++ .../executor/domain/ExecutorBase.java | 4 +- .../src/main/resources/application.properties | 3 - .../executor/domain/Executor.java | 48 +++++++------- .../src/main/resources/application.properties | 2 +- .../ch/unisg/roster/RosterApplication.java | 10 ++- .../in/web/TaskCompletedController.java | 1 - ...llExecutorInExecutorPoolByTypeAdapter.java | 63 ------------------- .../out/web/PublishNewTaskEventAdapter.java | 4 +- .../web/PublishTaskAssignedEventAdapter.java | 36 +++++------ .../web/PublishTaskCompletedEventAdapter.java | 11 +--- ...etAllExecutorInExecutorPoolByTypePort.java | 13 ---- .../src/main/resources/application.properties | 8 +-- .../src/main/resources/application.properties | 2 +- .../AddNewTaskToTaskListWebController.java | 2 + .../in/web/CompleteTaskWebController.java | 9 ++- .../in/web/TaskAssignedWebController.java | 4 +- .../service/AddNewTaskToTaskListService.java | 12 +--- .../service/CompleteTaskService.java | 6 +- .../service/TaskAssignedService.java | 2 +- .../unisg/tapastasks/tasks/domain/Task.java | 38 ++++++++--- .../tapastasks/tasks/domain/TaskList.java | 18 +++++- 22 files changed, 130 insertions(+), 175 deletions(-) delete mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java delete mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java diff --git a/.deployment/docker-compose.yml b/.deployment/docker-compose.yml index 1dab865..68ec014 100644 --- a/.deployment/docker-compose.yml +++ b/.deployment/docker-compose.yml @@ -49,6 +49,8 @@ services: restart: unless-stopped volumes: - ./:/data/ + environment: + mqtt.broker.uri: tcp://broker.hivemq.com:1883 labels: - "traefik.enable=true" - "traefik.http.routers.tapas-auction-house.rule=Host(`tapas-auction-house.${PUB_IP}.nip.io`)" @@ -64,6 +66,11 @@ services: restart: unless-stopped volumes: - ./:/data/ + environment: + task-list.uri: http://tapas-tasks:8081 + executor-robot.uri: http://executor-robot:8084 + executor-computation.uri: http://executor-computation:8085 + mqtt.broker.uri: tcp://broker.hivemq.com:1883 labels: - "traefik.enable=true" - "traefik.http.routers.roster.rule=Host(`roster.${PUB_IP}.nip.io`)" @@ -79,6 +86,8 @@ services: restart: unless-stopped volumes: - ./:/data/ + environment: + mqtt.broker.uri: tcp://broker.hivemq.com:1883 labels: - "traefik.enable=true" - "traefik.http.routers.executor-pool.rule=Host(`executor-pool.${PUB_IP}.nip.io`)" diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index 9f70dd1..09ded48 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -57,11 +57,11 @@ public abstract class ExecutorBase { **/ public void getAssignment() { Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getExecutorURI()); - System.out.println("New assignment"); - System.out.println(newTask); 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; } } diff --git a/executor-base/src/main/resources/application.properties b/executor-base/src/main/resources/application.properties index 4316ebf..5056e10 100644 --- a/executor-base/src/main/resources/application.properties +++ b/executor-base/src/main/resources/application.properties @@ -1,6 +1,3 @@ server.port=8081 roster.url=http://127.0.0.1:8082 executor.pool.url=http://127.0.0.1:8083 -executor1.url=http://127.0.0.1:8084 -executor2.url=http://127.0.0.1:8085 -task-list.url=http://127.0.0.1:8081 diff --git a/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java index 532099b..f281b4f 100644 --- a/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java +++ b/executor-computation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java @@ -21,8 +21,6 @@ public class Executor extends ExecutorBase { protected String execution(String inputData) { - System.out.println(inputData); - String operator = ""; if (inputData.contains("+")) { operator = "+"; @@ -30,34 +28,34 @@ public class Executor extends ExecutorBase { operator = "-"; } else if (inputData.contains("*")) { operator = "*"; + } else { + return "invalid data"; } - // System.out.println(operator); + double result = Double.NaN; - // double result = Double.NaN; - - // System.out.print(inputData.split("+")); - - // int a = Integer.parseInt(inputData.split(operator)[0]); - // int b = Integer.parseInt(inputData.split(operator)[1]); - - // // try { - // // TimeUnit.SECONDS.sleep(20); - // // } catch (InterruptedException e) { - // // e.printStackTrace(); - // // } - - // if (operator.equalsIgnoreCase("+")) { - // result = a + b; - // } else if (operator.equalsIgnoreCase("*")) { - // result = a * b; - // } else if (operator.equalsIgnoreCase("-")) { - // result = a - b; + // try { + // TimeUnit.SECONDS.sleep(5); + // } catch (InterruptedException e) { + // e.printStackTrace(); // } - // System.out.println("Result: " + result); - - double result = 0.0; + 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); } diff --git a/executor-pool/src/main/resources/application.properties b/executor-pool/src/main/resources/application.properties index c8fd60a..0c9ba7e 100644 --- a/executor-pool/src/main/resources/application.properties +++ b/executor-pool/src/main/resources/application.properties @@ -1,3 +1,3 @@ server.port=8083 -mqtt.broker.uri=tcp://broker.hivemq.com:1883 +mqtt.broker.uri=tcp://localhost:1883 diff --git a/roster/src/main/java/ch/unisg/roster/RosterApplication.java b/roster/src/main/java/ch/unisg/roster/RosterApplication.java index bc9ed86..1b12ca3 100644 --- a/roster/src/main/java/ch/unisg/roster/RosterApplication.java +++ b/roster/src/main/java/ch/unisg/roster/RosterApplication.java @@ -6,6 +6,7 @@ import java.util.logging.Logger; import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.ConfigurableEnvironment; import ch.unisg.roster.roster.adapter.common.clients.TapasMqttClient; import ch.unisg.roster.roster.adapter.in.messaging.mqtt.ExecutorEventMqttListener; @@ -16,11 +17,12 @@ public class RosterApplication { static Logger logger = Logger.getLogger(RosterApplication.class.getName()); - public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + private static ConfigurableEnvironment ENVIRONMENT; public static void main(String[] args) { - SpringApplication.run(RosterApplication.class, args); + SpringApplication rosterApp = new SpringApplication(RosterApplication.class); + ENVIRONMENT = rosterApp.run(args).getEnvironment(); bootstrapMarketplaceWithMqtt(); } @@ -29,9 +31,11 @@ public class RosterApplication { * one another */ private static void bootstrapMarketplaceWithMqtt() { + String broker = ENVIRONMENT.getProperty("mqtt.broker.uri"); + try { ExecutorEventsMqttDispatcher dispatcher = new ExecutorEventsMqttDispatcher(); - TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + TapasMqttClient client = TapasMqttClient.getInstance(broker, dispatcher); client.startReceivingMessages(); } catch (MqttException e) { logger.log(Level.SEVERE, e.getMessage(), e); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java index 5adfd7e..a910ee2 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java @@ -25,7 +25,6 @@ public class TaskCompletedController { **/ @PostMapping(path = "/task/completed", consumes = {"application/json"}) public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { - System.out.println("TEST"); TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), task.getStatus(), task.getOutputData()); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java deleted file mode 100644 index df444ca..0000000 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java +++ /dev/null @@ -1,63 +0,0 @@ -package ch.unisg.roster.roster.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.json.JSONArray; -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.roster.roster.application.port.out.GetAllExecutorInExecutorPoolByTypePort; -import ch.unisg.roster.roster.domain.valueobject.ExecutorType; - -@Component -@Primary -public class GetAllExecutorInExecutorPoolByTypeAdapter implements GetAllExecutorInExecutorPoolByTypePort { - - @Value("${executor-pool.url}") - private String server; - - /** - * Requests all executor of the give type from the executor-pool and cheks if there is one - * avaialable of this type. - * @return Whether an executor exist or not - **/ - @Override - public boolean doesExecutorTypeExist(ExecutorType type) { - - Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); - - - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/executor-pool/GetAllExecutorInExecutorPoolByType/" + type.getValue())) - .header("Content-Type", "application/json") - .GET() - .build(); - - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == HttpStatus.OK.value()) { - JSONArray jsonArray = new JSONArray(response.body()); - if (jsonArray.length() > 0) { - return true; - } - } - } catch (InterruptedException e) { - logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - Thread.currentThread().interrupt(); - } catch (IOException e) { - logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - } - return false; - } - -} diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java index 274d639..c16961f 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java @@ -19,10 +19,10 @@ import ch.unisg.roster.roster.domain.event.NewTaskEvent; @Primary public class PublishNewTaskEventAdapter implements NewTaskEventPort { - @Value("${executor1.url}") + @Value("${executor-robot.uri}") private String server; - @Value("${executor2.url}") + @Value("${executor-computation.uri}") private String server2; Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java index 2c75a03..105d464 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -20,7 +20,7 @@ import ch.unisg.roster.roster.domain.event.TaskAssignedEvent; @Primary public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { - @Value("${task-list.url}") + @Value("${task-list.uri}") private String server; Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName()); @@ -32,26 +32,26 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { @Override public void publishTaskAssignedEvent(TaskAssignedEvent event) { - // String body = new JSONObject() - // .put("taskId", event.taskID) - // .toString(); + String body = new JSONObject() + .put("taskId", event.taskID) + .toString(); - // HttpClient client = HttpClient.newHttpClient(); - // HttpRequest request = HttpRequest.newBuilder() - // .uri(URI.create(server + "/tasks/assignTask")) - // .header("Content-Type", "application/task+json") - // .POST(HttpRequest.BodyPublishers.ofString(body)) - // .build(); + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server + "/tasks/assignTask")) + .header("Content-Type", "application/task+json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .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); - // } + 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); + } } } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java index 3773621..50d72ae 100644 --- a/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -20,7 +20,7 @@ import ch.unisg.roster.roster.domain.event.TaskCompletedEvent; @Primary public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort { - @Value("${task-list.url}") + @Value("${task-list.uri}") private String server; Logger logger = Logger.getLogger(PublishTaskCompletedEventAdapter.class.getName()); @@ -32,22 +32,17 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort @Override public void publishTaskCompleted(TaskCompletedEvent event) { - System.out.println("PublishTaskCompletedEventAdapter.publishTaskCompleted()"); - System.out.print(server); - String body = new JSONObject() .put("taskId", event.taskID) .put("status", event.status) .put("outputData", event.result) .toString(); - System.out.println(event.taskID); - HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(server + "/tasks/completeTask/" + event.taskID)) + .uri(URI.create(server + "/tasks/completeTask/")) .header("Content-Type", "application/task+json") - .GET() + .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java deleted file mode 100644 index f32a3f5..0000000 --- a/roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java +++ /dev/null @@ -1,13 +0,0 @@ -package ch.unisg.roster.roster.application.port.out; - -import ch.unisg.roster.roster.domain.valueobject.ExecutorType; - -public interface GetAllExecutorInExecutorPoolByTypePort { - /** - * Checks if a executor with the given type exist in our executor pool - * @return boolean - **/ - boolean doesExecutorTypeExist(ExecutorType type); -} - - diff --git a/roster/src/main/resources/application.properties b/roster/src/main/resources/application.properties index dc443ab..b7233ff 100644 --- a/roster/src/main/resources/application.properties +++ b/roster/src/main/resources/application.properties @@ -1,5 +1,5 @@ server.port=8082 -executor-pool.url=http://127.0.0.1:8083 -executor1.url=http://127.0.0.1:8084 -executor2.url=http://127.0.0.1:8085 -task-list.url=http://127.0.0.1:8081 +executor-robot.uri=http://127.0.0.1:8084 +executor-computation.uri=http://127.0.0.1:8085 +task-list.uri=http://127.0.0.1:8081 +mqtt.broker.uri=tcp://localhost:1883 diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index e3900bd..7f8ca02 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -10,4 +10,4 @@ tasks.list.uri=http://localhost:8081 application.environment=development auctionhouse.uri=http://localhost:8086 websub.hub.uri=http://localhost:3000 -mqtt.broker.uri=tcp://broker.hivemq.com:1883 +mqtt.broker.uri=tcp://localhost:1883 diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java index 15c3ebb..679b1e3 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebController.java @@ -78,6 +78,8 @@ public class AddNewTaskToTaskListWebController { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } catch (ConstraintViolationException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } catch (NullPointerException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java index fa5578b..02bdec3 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java @@ -26,17 +26,16 @@ public class CompleteTaskWebController { this.completeTaskUseCase = completeTaskUseCase; } - @GetMapping(path = "/tasks/completeTask/{taskId}") - public ResponseEntity completeTask (@PathVariable("taskId") String taskId){ + @PostMapping(path = "/tasks/completeTask") + public ResponseEntity completeTask (@RequestBody TaskJsonRepresentation payload) { System.out.println("completeTask"); - System.out.println(taskId); + System.out.println(payload.getTaskId()); - String taskResult = "0"; try { CompleteTaskCommand command = new CompleteTaskCommand( - new Task.TaskId(taskId), new Task.OutputData(taskResult) + new Task.TaskId(payload.getTaskId()), new Task.OutputData(payload.getOutputData()) ); Task updateATask = completeTaskUseCase.completeTask(command); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java index b58d159..1bec71a 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java @@ -24,10 +24,10 @@ public class TaskAssignedWebController { } @PostMapping(path="/tasks/assignTask", consumes= {TaskJsonRepresentation.MEDIA_TYPE}) - public ResponseEntity assignTask(@RequestBody Task task){ + public ResponseEntity assignTask(@RequestBody TaskJsonRepresentation payload) { try{ TaskAssignedCommand command = new TaskAssignedCommand( - task.getTaskId() + new Task.TaskId(payload.getTaskId()) ); Task updateATask = taskAssignedUseCase.assignTask(command); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 26234ce..d7b9740 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -24,25 +24,19 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase Task newTask; - System.out.println("TEST:"); - System.out.println(command.getInputData().get()); - if (command.getOriginalTaskUri().isPresent() && command.getInputData().isPresent()) { - System.out.println("TEST2:"); newTask = taskList.addNewTaskWithNameAndTypeAndOriginalTaskUriAndInputData(command.getTaskName(), command.getTaskType(), command.getOriginalTaskUri().get(), command.getInputData().get()); } else if (command.getOriginalTaskUri().isPresent()) { newTask = taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), command.getTaskType(), command.getOriginalTaskUri().get()); - } else if (command.getOriginalTaskUri().isPresent()) { - newTask = null; + } else if (command.getInputData().isPresent()) { + newTask = taskList.addNewTaskWithNameAndTypeAndInputData(command.getTaskName(), + command.getTaskType(), command.getInputData().get()); } else { newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); } - System.out.println("TEST"); - System.out.println(newTask.getInputData()); - //Here we are using the application service to emit the domain event to the outside of the bounded context. //This event should be considered as a light-weight "integration event" to communicate with other services. //Domain events are usually rather "fat". In our implementation we simplify at this point. In general, it is diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java index df22421..8ded0ea 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java @@ -26,10 +26,10 @@ public class CompleteTaskService implements CompleteTaskUseCase { Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); Task newTask = updatedTask.get(); - newTask.taskResult = new TaskResult(command.getOutputData().getValue()); - newTask.taskStatus = new TaskStatus(Task.Status.EXECUTED); + newTask.setOutputData(command.getOutputData()); + newTask.setTaskStatus(new TaskStatus(Task.Status.EXECUTED)); - if (!newTask.getOriginalTaskUri().getValue().equalsIgnoreCase("")) { + if (newTask.getOriginalTaskUri() != null) { ExternalTaskExecutedEvent event = new ExternalTaskExecutedEvent( newTask.getTaskId(), newTask.getOriginalTaskUri(), diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java index 6c580e4..fc13db3 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java @@ -24,7 +24,7 @@ public class TaskAssignedService implements TaskAssignedUseCase { // update the status to assigned Task updatedTask = task.get(); - updatedTask.taskStatus = new TaskStatus(Status.ASSIGNED); + updatedTask.setTaskStatus(new TaskStatus(Status.ASSIGNED)); return updatedTask; } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index d07f0f1..aca71dd 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -22,10 +22,7 @@ public class Task { private final TaskType taskType; @Getter @Setter - public TaskStatus taskStatus; // had to make public for CompleteTaskService - - @Getter - public TaskResult taskResult; // same as above + private TaskStatus taskStatus; @Getter private final OriginalTaskUri originalTaskUri; @@ -39,24 +36,44 @@ public class Task { @Getter @Setter private OutputData outputData; + public Task(TaskName taskName, TaskType taskType) { + this.taskName = taskName; + this.taskType = taskType; + this.taskStatus = new TaskStatus(Status.OPEN); + this.taskId = new TaskId(UUID.randomUUID().toString()); + this.originalTaskUri = null; + + this.inputData = null; + this.outputData = null; + } + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) { this.taskName = taskName; this.taskType = taskType; this.taskStatus = new TaskStatus(Status.OPEN); this.taskId = new TaskId(UUID.randomUUID().toString()); - this.taskResult = new TaskResult(""); this.originalTaskUri = taskUri; this.inputData = null; this.outputData = null; } + public Task(TaskName taskName, TaskType taskType, InputData inputData) { + this.taskName = taskName; + this.taskType = taskType; + this.taskStatus = new TaskStatus(Status.OPEN); + this.taskId = new TaskId(UUID.randomUUID().toString()); + this.originalTaskUri = null; + + this.inputData = inputData; + this.outputData = null; + } + public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, InputData inputData) { this.taskName = taskName; this.taskType = taskType; this.taskStatus = new TaskStatus(Status.OPEN); this.taskId = new TaskId(UUID.randomUUID().toString()); - this.taskResult = new TaskResult(""); this.originalTaskUri = taskUri; this.inputData = inputData; @@ -64,9 +81,7 @@ public class Task { } protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { - //This is a simple debug message to see that the request has reached the right method in the core - System.out.println("New Task: " + name.getValue() + " " + type.getValue()); - return new Task(name, type, null); + return new Task(name, type); } protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, @@ -74,6 +89,11 @@ public class Task { return new Task(name, type, originalTaskUri); } + protected static Task createTaskWithNameAndTypeAndInputData(TaskName name, TaskType type, + InputData inputData) { + return new Task(name, type, inputData); + } + protected static Task createTaskWithNameAndTypeAndOriginalTaskUriAndInputData(TaskName name, TaskType type, OriginalTaskUri originalTaskUri, InputData inputData) { return new Task(name, type, originalTaskUri, inputData); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java index 72160e8..5c2913d 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java @@ -6,11 +6,15 @@ import lombok.Value; import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; /**This is our aggregate root**/ public class TaskList { + Logger logger = Logger.getLogger(TaskList.class.getName()); + @Getter private final TaskListName taskListName; @@ -48,6 +52,14 @@ public class TaskList { return newTask; } + public Task addNewTaskWithNameAndTypeAndInputData(Task.TaskName name, Task.TaskType type, + Task.InputData inputData) { + Task newTask = Task.createTaskWithNameAndTypeAndInputData(name, type, inputData); + this.addNewTaskToList(newTask); + + return newTask; + } + public Task addNewTaskWithNameAndTypeAndOriginalTaskUriAndInputData(Task.TaskName name, Task.TaskType type, Task.OriginalTaskUri originalTaskUri, Task.InputData inputData) { Task newTask = Task.createTaskWithNameAndTypeAndOriginalTaskUriAndInputData(name, type, originalTaskUri, inputData); @@ -62,8 +74,10 @@ public class TaskList { //However, we skip this here as it makes the core even more complex (e.g., we have to implement a light-weight //domain event publisher and subscribers (see "Implementing Domain-Driven Design by V. Vernon, pp. 296ff). listOfTasks.value.add(newTask); - //This is a simple debug message to see that the task list is growing with each new request - System.out.println("Number of tasks: " + listOfTasks.value.size()); + logger.log(Level.INFO, "New task created! Id: " + newTask.getTaskId().getValue() + + " | Name: " + newTask.getTaskName().getValue() + + " | InputData: " + newTask.getInputData().getValue()); + logger.log(Level.INFO, "Number of tasks: {0}", listOfTasks.value.size()); } public Optional retrieveTaskById(Task.TaskId id) { -- 2.45.1 From c126c349720617b9d24f9c4487b181fefea7a3e8 Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 18 Nov 2021 16:02:28 +0100 Subject: [PATCH 87/94] First set tests for Assignment 8 --- tapas-tasks/pom.xml | 19 +++++ .../service/AddNewTaskToTaskListService.java | 3 + .../RetrieveTaskFromTaskListService.java | 2 + .../src/main/resources/application.properties | 35 ++++++++- .../AddNewTaskToTaskListSystemTest.java | 76 ++++++++++++++++++ .../TapasTasksApplicationTests.java | 7 +- ...AddNewTaskToTaskListWebControllerTest.java | 68 ++++++++++++++++ .../mongodb/TaskPersistenceAdapterTest.java | 77 +++++++++++++++++++ .../AddNewTaskToTaskListServiceTest.java | 63 +++++++++++++++ .../tapastasks/tasks/domain/TaskListTest.java | 49 ++++++++++++ .../resources/application-test.properties | 2 + 11 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java create mode 100644 tapas-tasks/src/test/resources/application-test.properties diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 7dcf6ae..6dcc158 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -37,6 +37,16 @@ spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-actuator + + + de.codecentric + chaos-monkey-spring-boot + 2.5.4 + + org.projectlombok lombok @@ -47,6 +57,11 @@ spring-boot-starter-test test + + org.mockito + mockito-core + 2.21.0 + org.springframework.boot @@ -74,6 +89,10 @@ org.eclipse.paho.client.mqttv3 1.2.0 + + org.springframework.boot + spring-boot-test + diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index d78de83..9776064 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -11,11 +11,14 @@ import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + import javax.transaction.Transactional; @RequiredArgsConstructor @Component @Transactional +@Service("AddNewTaskToTaskList") public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase { private final NewTaskAddedEventPort newTaskAddedEventPort; diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java index 5e5ec29..39026f5 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java @@ -7,6 +7,7 @@ import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.Optional; @@ -14,6 +15,7 @@ import java.util.Optional; @RequiredArgsConstructor @Component @Transactional +@Service("RetrieveTaskFromTaskList") public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase { private final LoadTaskPort loadTaskFromRepositoryPort; diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index badc0f8..7588f89 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1,7 +1,38 @@ server.port=8081 -//spring.data.mongodb.uri=mongodb://127.0.0.1:27017 -spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ +spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +#spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ spring.data.mongodb.database=tapas-tasks baseuri=https://tapas-tasks.86-119-34-23.nip.io/ +spring.profiles.active=chaos-monkey +chaos.monkey.enabled=true +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 +#Chaos Monkey configs taken from here: https://www.baeldung.com/spring-boot-chaos-monkey + +#Latency Assault +#chaos.monkey.assaults.latencyActive=true +#chaos.monkey.assaults.latencyRangeStart=3000 +#chaos.monkey.assaults.latencyRangeEnd=15000 + +#Exception Assault +#chaos.monkey.assaults.latencyActive=false +#chaos.monkey.assaults.exceptionsActive=true +#chaos.monkey.assaults.killApplicationActive=false + +#AppKiller Assault +#chaos.monkey.assaults.latencyActive=false +#chaos.monkey.assaults.exceptionsActive=false +#chaos.monkey.assaults.killApplicationActive=true + +#Chaos Monkey assaults via REST to endpoint /actuator/chaosmonkey/assaults/ +#https://softwarehut.com/blog/tech/chaos-monkey +#https://codecentric.github.io/chaos-monkey-spring-boot/latest/ diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java new file mode 100644 index 0000000..e17c2e2 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java @@ -0,0 +1,76 @@ +package ch.unisg.tapastasks; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; + +import static org.assertj.core.api.BDDAssertions.*; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class AddNewTaskToTaskListSystemTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private AddTaskPort addTaskPort; + + @Test + void addNewTaskToTaskList() throws JSONException { + + Task.TaskName taskName = new Task.TaskName("system-integration-test-task"); + Task.TaskType taskType = new Task.TaskType("system-integration-test-type"); + Task.OriginalTaskUri originalTaskUri = new Task.OriginalTaskUri("example.org"); + + ResponseEntity response = whenAddNewTaskToEmptyList(taskName, taskType, originalTaskUri); + + JSONObject responseJson = new JSONObject(response.getBody().toString()); + String respTaskId = responseJson.getString("taskId"); + String respTaskName = responseJson.getString("taskName"); + String respTaskType = responseJson.getString("taskType"); + + then(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + then(respTaskId).isNotEmpty(); + then(respTaskName).isEqualTo(taskName.getValue()); + then(respTaskType).isEqualTo(taskType.getValue()); + then(TaskList.getTapasTaskList().getListOfTasks().getValue()).hasSize(1); + + } + + private ResponseEntity whenAddNewTaskToEmptyList( + Task.TaskName taskName, + Task.TaskType taskType, + Task.OriginalTaskUri originalTaskUri) throws JSONException { + + TaskList.getTapasTaskList().getListOfTasks().getValue().clear(); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", TaskJsonRepresentation.MEDIA_TYPE); + + String jsonPayLoad = new JSONObject() + .put("taskName", taskName.getValue() ) + .put("taskType", taskType.getValue()) + .put("originalTaskUri",originalTaskUri.getValue()) + .toString(); + + HttpEntity request = new HttpEntity<>(jsonPayLoad,headers); + + return restTemplate.exchange( + "/tasks/", + HttpMethod.POST, + request, + Object.class + ); + + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java index b96cb8b..a343151 100644 --- a/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java @@ -1,13 +1,18 @@ package ch.unisg.tapastasks; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.application.service.AddNewTaskToTaskListService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class TapasTasksApplicationTests { - @Test + @Test void contextLoads() { + } } diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java new file mode 100644 index 0000000..d397908 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java @@ -0,0 +1,68 @@ +package ch.unisg.tapastasks.tasks.adapter.in.web; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; +import ch.unisg.tapastasks.tasks.adapter.in.web.AddNewTaskToTaskListWebController; +import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository; +import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand; +import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase; +import ch.unisg.tapastasks.tasks.domain.Task; +import org.json.JSONObject; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Optional; + +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.then; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(controllers = AddNewTaskToTaskListWebController.class) +public class AddNewTaskToTaskListWebControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase; + + @MockBean + TaskRepository taskRepository; + + @Disabled + @Test + void testAddNewTaskToTaskList() throws Exception { + + String taskName = "test-request"; + String taskType = "test-request-type"; + String originalTaskUri = "example.org"; + + String jsonPayLoad = new JSONObject() + .put("taskName", taskName ) + .put("taskType", taskType) + .put("originalTaskUri",originalTaskUri) + .toString(); + + //This raises a NullPointerException since it tries to build the HTTP response with attributes from + //the domain object (created task), which is mocked --> we need System Tests here! + //See the buckpal example from the lecture for a working integration test for testing the web controller + mockMvc.perform(post("/tasks/") + .contentType(TaskJsonRepresentation.MEDIA_TYPE) + .content(jsonPayLoad)) + .andExpect(status().isCreated()); + + then(addNewTaskToTaskListUseCase).should() + .addNewTaskToTaskList(eq(new AddNewTaskToTaskListCommand( + new Task.TaskName(taskName), new Task.TaskType(taskType), + Optional.of(new Task.OriginalTaskUri(originalTaskUri)) + ))); + + } + + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java new file mode 100644 index 0000000..ef1fa99 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java @@ -0,0 +1,77 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureDataMongo +@Import({TaskPersistenceAdapter.class, TaskMapper.class}) +public class TaskPersistenceAdapterTest { + + @Autowired + private TaskRepository taskRepository; + + @Autowired + private TaskPersistenceAdapter adapterUnderTest; + + @Test + void addsNewTask() { + + String testTaskId = UUID.randomUUID().toString(); + String testTaskName = "adds-persistence-task-name"; + String testTaskType = "adds-persistence-task-type"; + String testTaskOuri = "adds-persistence-test-task-ouri"; + String testTaskStatus = Task.Status.OPEN.toString(); + String testTaskListName = "tapas-tasks-tutors"; + + + Task testTask = new Task( + new Task.TaskId(testTaskId), + new Task.TaskName(testTaskName), + new Task.TaskType(testTaskType), + new Task.OriginalTaskUri(testTaskOuri), + new Task.TaskStatus(Task.Status.valueOf(testTaskStatus)) + ); + adapterUnderTest.addTask(testTask); + + MongoTaskDocument retrievedDoc = taskRepository.findByTaskId(testTaskId,testTaskListName); + + assertThat(retrievedDoc.taskId).isEqualTo(testTaskId); + assertThat(retrievedDoc.taskName).isEqualTo(testTaskName); + assertThat(retrievedDoc.taskListName).isEqualTo(testTaskListName); + + } + + @Test + void retrievesTask() { + + String testTaskId = UUID.randomUUID().toString(); + String testTaskName = "reads-persistence-task-name"; + String testTaskType = "reads-persistence-task-type"; + String testTaskOuri = "reads-persistence-test-task-ouri"; + String testTaskStatus = Task.Status.OPEN.toString(); + String testTaskListName = "tapas-tasks-tutors"; + + MongoTaskDocument mongoTask = new MongoTaskDocument(testTaskId, testTaskName, testTaskType, testTaskOuri, + testTaskStatus, testTaskListName); + taskRepository.insert(mongoTask); + + Task retrievedTask = adapterUnderTest.loadTask(new Task.TaskId(testTaskId), + new TaskList.TaskListName(testTaskListName)); + + assertThat(retrievedTask.getTaskName().getValue()).isEqualTo(testTaskName); + assertThat(retrievedTask.getTaskId().getValue()).isEqualTo(testTaskId); + assertThat(retrievedTask.getTaskStatus().getValue()).isEqualTo(Task.Status.valueOf(testTaskStatus)); + + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java new file mode 100644 index 0000000..10d21fd --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java @@ -0,0 +1,63 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.mockito.BDDMockito.*; +import static org.assertj.core.api.Assertions.*; + + +public class AddNewTaskToTaskListServiceTest { + + private final AddTaskPort addTaskPort = Mockito.mock(AddTaskPort.class); + private final TaskListLock taskListLock = Mockito.mock(TaskListLock.class); + private final NewTaskAddedEventPort newTaskAddedEventPort = Mockito.mock(NewTaskAddedEventPort.class); + private final AddNewTaskToTaskListService addNewTaskToTaskListService = new AddNewTaskToTaskListService( + newTaskAddedEventPort, addTaskPort, taskListLock); + + @Test + void addingSucceeds() { + + Task newTask = givenATaskWithNameAndTypeAndURI(new Task.TaskName("test-task"), + new Task.TaskType("test-type"), Optional.of(new Task.OriginalTaskUri("example.org"))); + + TaskList taskList = givenAnEmptyTaskList(TaskList.getTapasTaskList()); + + AddNewTaskToTaskListCommand addNewTaskToTaskListCommand = new AddNewTaskToTaskListCommand(newTask.getTaskName(), + newTask.getTaskType(), Optional.ofNullable(newTask.getOriginalTaskUri())); + + Task addedTask = addNewTaskToTaskListService.addNewTaskToTaskList(addNewTaskToTaskListCommand); + + assertThat(addedTask).isNotNull(); + assertThat(taskList.getListOfTasks().getValue()).hasSize(1); + + then(taskListLock).should().lockTaskList(eq(TaskList.getTapasTaskList().getTaskListName())); + then(newTaskAddedEventPort).should(times(1)) + .publishNewTaskAddedEvent(any(NewTaskAddedEvent.class)); + + } + + private TaskList givenAnEmptyTaskList(TaskList taskList) { + taskList.getListOfTasks().getValue().clear(); + return taskList; + } + + private Task givenATaskWithNameAndTypeAndURI(Task.TaskName taskName, Task.TaskType taskType, + Optional originalTaskUri) { + Task task = Mockito.mock(Task.class); + given(task.getTaskName()).willReturn(taskName); + given(task.getTaskType()).willReturn(taskType); + given(task.getOriginalTaskUri()).willReturn(originalTaskUri.get()); + return task; + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java new file mode 100644 index 0000000..05dc664 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java @@ -0,0 +1,49 @@ +package ch.unisg.tapastasks.tasks.domain; + +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; + + +public class TaskListTest { + + @Test + void addNewTaskToTaskListSuccess() { + TaskList taskList = TaskList.getTapasTaskList(); + taskList.getListOfTasks().getValue().clear(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task"), + new Task.TaskType("My-Test-Type")); + + assertThat(newTask.getTaskName().getValue()).isEqualTo("My-Test-Task"); + assertThat(taskList.getListOfTasks().getValue()).hasSize(1); + assertThat(taskList.getListOfTasks().getValue().get(0)).isEqualTo(newTask); + + } + + @Test + void retrieveTaskSuccess() { + TaskList taskList = TaskList.getTapasTaskList(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task2"), + new Task.TaskType("My-Test-Type2")); + + Task retrievedTask = taskList.retrieveTaskById(newTask.getTaskId()).get(); + + assertThat(retrievedTask).isEqualTo(newTask); + + } + + @Test + void retrieveTaskFailure() { + TaskList taskList = TaskList.getTapasTaskList(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task3"), + new Task.TaskType("My-Test-Type3")); + + Task.TaskId fakeId = new Task.TaskId("fake-id"); + + Optional retrievedTask = taskList.retrieveTaskById(fakeId); + + assertThat(retrievedTask.isPresent()).isFalse(); + } +} diff --git a/tapas-tasks/src/test/resources/application-test.properties b/tapas-tasks/src/test/resources/application-test.properties new file mode 100644 index 0000000..e45b53d --- /dev/null +++ b/tapas-tasks/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +spring.data.mongodb.database=tapas-tasks -- 2.45.1 From f6c0495c047023a010097ec6f0a6559526766c72 Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 18 Nov 2021 18:13:49 +0100 Subject: [PATCH 88/94] ArchUnit test for Assignment 8 --- tapas-tasks/pom.xml | 6 ++ .../unisg/tapastasks/DependencyRuleTests.java | 24 ++++++ .../unisg/tapastasks/archunit/Adapters.java | 62 ++++++++++++++++ .../tapastasks/archunit/ApplicationLayer.java | 59 +++++++++++++++ .../archunit/ArchitectureElement.java | 74 +++++++++++++++++++ .../archunit/HexagonalArchitecture.java | 61 +++++++++++++++ 6 files changed, 286 insertions(+) create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 6dcc158..25ef1f2 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -62,6 +62,12 @@ mockito-core 2.21.0 + + com.tngtech.archunit + archunit-junit4 + 0.22.0 + test + org.springframework.boot diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java new file mode 100644 index 0000000..0d26f37 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java @@ -0,0 +1,24 @@ +package ch.unisg.tapastasks; + +import ch.unisg.tapastasks.archunit.HexagonalArchitecture; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +class DependencyRuleTests { + + @Test + void testPackageDependencies() { + noClasses() + .that() + .resideInAPackage("ch.unisg.tapastasks.tasks.domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("ch.unisg.tapastasks.tasks.application..") + .check(new ClassFileImporter() + .importPackages("ch.unisg.tapastasks.tasks..")); + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java new file mode 100644 index 0000000..92a764b --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java @@ -0,0 +1,62 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.List; + +public class Adapters extends ArchitectureElement { + + private final HexagonalArchitecture parentContext; + private List incomingAdapterPackages = new ArrayList<>(); + private List outgoingAdapterPackages = new ArrayList<>(); + + Adapters(HexagonalArchitecture parentContext, String basePackage) { + super(basePackage); + this.parentContext = parentContext; + } + + public Adapters outgoing(String packageName) { + this.incomingAdapterPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public Adapters incoming(String packageName) { + this.outgoingAdapterPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + List allAdapterPackages() { + List allAdapters = new ArrayList<>(); + allAdapters.addAll(incomingAdapterPackages); + allAdapters.addAll(outgoingAdapterPackages); + return allAdapters; + } + + public HexagonalArchitecture and() { + return parentContext; + } + + String getBasePackage() { + return basePackage; + } + + void dontDependOnEachOther(JavaClasses classes) { + List allAdapters = allAdapterPackages(); + for (String adapter1 : allAdapters) { + for (String adapter2 : allAdapters) { + if (!adapter1.equals(adapter2)) { + denyDependency(adapter1, adapter2, classes); + } + } + } + } + + void doesNotDependOn(String packageName, JavaClasses classes) { + denyDependency(this.basePackage, packageName, classes); + } + + void doesNotContainEmptyPackages() { + denyEmptyPackages(allAdapterPackages()); + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java new file mode 100644 index 0000000..e8e00a5 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java @@ -0,0 +1,59 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationLayer extends ArchitectureElement { + + private final HexagonalArchitecture parentContext; + private List incomingPortsPackages = new ArrayList<>(); + private List outgoingPortsPackages = new ArrayList<>(); + private List servicePackages = new ArrayList<>(); + + public ApplicationLayer(String basePackage, HexagonalArchitecture parentContext) { + super(basePackage); + this.parentContext = parentContext; + } + + public ApplicationLayer incomingPorts(String packageName) { + this.incomingPortsPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public ApplicationLayer outgoingPorts(String packageName) { + this.outgoingPortsPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public ApplicationLayer services(String packageName) { + this.servicePackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public HexagonalArchitecture and() { + return parentContext; + } + + public void doesNotDependOn(String packageName, JavaClasses classes) { + denyDependency(this.basePackage, packageName, classes); + } + + public void incomingAndOutgoingPortsDoNotDependOnEachOther(JavaClasses classes) { + denyAnyDependency(this.incomingPortsPackages, this.outgoingPortsPackages, classes); + denyAnyDependency(this.outgoingPortsPackages, this.incomingPortsPackages, classes); + } + + private List allPackages() { + List allPackages = new ArrayList<>(); + allPackages.addAll(incomingPortsPackages); + allPackages.addAll(outgoingPortsPackages); + allPackages.addAll(servicePackages); + return allPackages; + } + + void doesNotContainEmptyPackages() { + denyEmptyPackages(allPackages()); + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java new file mode 100644 index 0000000..465f441 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java @@ -0,0 +1,74 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; + +import java.util.List; + +import static com.tngtech.archunit.base.DescribedPredicate.*; +import static com.tngtech.archunit.base.DescribedPredicate.greaterThanOrEqualTo; +import static com.tngtech.archunit.lang.conditions.ArchConditions.*; +import static com.tngtech.archunit.lang.conditions.ArchConditions.containNumberOfElements; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +abstract class ArchitectureElement { + + final String basePackage; + + public ArchitectureElement(String basePackage) { + this.basePackage = basePackage; + } + + String fullQualifiedPackage(String relativePackage) { + return this.basePackage + "." + relativePackage; + } + + static void denyDependency(String fromPackageName, String toPackageName, JavaClasses classes) { + noClasses() + .that() + .resideInAPackage("ch.unisg.tapastasks.tasks.domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("ch.unisg.tapastasks.tasks.application..") + .check(classes); + } + + static void denyAnyDependency( + List fromPackages, List toPackages, JavaClasses classes) { + for (String fromPackage : fromPackages) { + for (String toPackage : toPackages) { + noClasses() + .that() + .resideInAPackage(matchAllClassesInPackage(fromPackage)) + .should() + .dependOnClassesThat() + .resideInAnyPackage(matchAllClassesInPackage(toPackage)) + .check(classes); + } + } + } + + static String matchAllClassesInPackage(String packageName) { + return packageName + ".."; + } + + void denyEmptyPackage(String packageName) { + classes() + .that() + .resideInAPackage(matchAllClassesInPackage(packageName)) + .should(containNumberOfElements(greaterThanOrEqualTo(1))) + .check(classesInPackage(packageName)); + } + + private JavaClasses classesInPackage(String packageName) { + return new ClassFileImporter().importPackages(packageName); + } + + void denyEmptyPackages(List packages) { + for (String packageName : packages) { + denyEmptyPackage(packageName); + } + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java new file mode 100644 index 0000000..1445968 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java @@ -0,0 +1,61 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HexagonalArchitecture extends ArchitectureElement { + + private Adapters adapters; + private ApplicationLayer applicationLayer; + private String configurationPackage; + private List domainPackages = new ArrayList<>(); + + public static HexagonalArchitecture boundedContext(String basePackage) { + return new HexagonalArchitecture(basePackage); + } + + public HexagonalArchitecture(String basePackage) { + super(basePackage); + } + + public Adapters withAdaptersLayer(String adaptersPackage) { + this.adapters = new Adapters(this, fullQualifiedPackage(adaptersPackage)); + return this.adapters; + } + + public HexagonalArchitecture withDomainLayer(String domainPackage) { + this.domainPackages.add(fullQualifiedPackage(domainPackage)); + return this; + } + + public ApplicationLayer withApplicationLayer(String applicationPackage) { + this.applicationLayer = new ApplicationLayer(fullQualifiedPackage(applicationPackage), this); + return this.applicationLayer; + } + + public HexagonalArchitecture withConfiguration(String packageName) { + this.configurationPackage = fullQualifiedPackage(packageName); + return this; + } + + private void domainDoesNotDependOnOtherPackages(JavaClasses classes) { + denyAnyDependency( + this.domainPackages, Collections.singletonList(adapters.basePackage), classes); + denyAnyDependency( + this.domainPackages, Collections.singletonList(applicationLayer.basePackage), classes); + } + + public void check(JavaClasses classes) { + this.adapters.doesNotContainEmptyPackages(); + this.adapters.dontDependOnEachOther(classes); + this.adapters.doesNotDependOn(this.configurationPackage, classes); + this.applicationLayer.doesNotContainEmptyPackages(); + this.applicationLayer.doesNotDependOn(this.adapters.getBasePackage(), classes); + this.applicationLayer.doesNotDependOn(this.configurationPackage, classes); + this.applicationLayer.incomingAndOutgoingPortsDoNotDependOnEachOther(classes); + this.domainDoesNotDependOnOtherPackages(classes); + } +} -- 2.45.1 From e3768280ac8b4cd047c736519c15a70f56ca5093 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 22:09:07 +0100 Subject: [PATCH 89/94] using https for auction house link --- .../main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java | 2 +- .../tapas/auctionhouse/adapter/in/web/AddBidWebController.java | 2 +- tapas-auction-house/src/main/resources/application.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 9fe963e..476c7d0 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -58,7 +58,7 @@ public class TapasAuctionHouseApplication { private static void bootstrapMarketplaceWithMqtt() { try { String broker = ENVIRONMENT.getProperty("mqtt.broker.uri"); - + LOGGER.info("Bootstrapping Mqtt"); if (broker == null) { broker = DEFAULT_MQTT_BROKER; LOGGER.info("No MQTT broker was set in application.propreties, going with default: " diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java index 41e65ff..e95419e 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/AddBidWebController.java @@ -32,7 +32,7 @@ public class AddBidWebController { new Bid.BidderTaskListUri(URI.create(payload.getBidderTaskListUri())) )); - LOGGER.info("Bid received", payload); + LOGGER.info("Bid received:" + payload); BidReceivedHandler bidReceivedHandler = new BidReceivedHandler(); bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent); diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 7f8ca02..78bda13 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -4,7 +4,7 @@ websub.hub=https://websub.appspot.com/ websub.hub.publish=https://websub.appspot.com/ group=tapas-group-tutors -auction.house.uri=http://tapas-auction-house.86-119-35-40.nip.io +auction.house.uri=https://tapas-auction-house.86-119-35-40.nip.io tasks.list.uri=http://localhost:8081 application.environment=development -- 2.45.1 From 3b205d80a5b51319e43bdbf59d93bce2ed2ccf22 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 22:39:20 +0100 Subject: [PATCH 90/94] using the bidders auction house uri --- .../adapter/out/web/AuctionWonEventHttpAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java index 4583892..2178fb4 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java @@ -57,9 +57,10 @@ public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { new Task.ServiceProvider("TODO") ); + var bidderAuctionHouseUri = event.getWinningBid().getBidderAuctionHouseUri().getValue().toString(); String body = TaskJsonRepresentation.serialize(task); LOGGER.info(body); - var postURI = URI.create(auction.get().getAuctionHouseUri().getValue().toString() + "/taskwinner"); + var postURI = URI.create(bidderAuctionHouseUri + "/taskwinner"); HttpRequest postRequest = HttpRequest.newBuilder() .uri(postURI) .header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE) -- 2.45.1 From 332f035622ec0d445eec1193cc06738f2e2be50f Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 22:53:52 +0100 Subject: [PATCH 91/94] small fixes --- .../mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java | 4 ++-- .../adapter/out/web/AuctionWonEventHttpAdapter.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java index c47acad..746d0ba 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/mqtt/ExternalAuctionStartedEventListenerMqttAdapter.java @@ -25,9 +25,9 @@ import java.sql.Timestamp; public class ExternalAuctionStartedEventListenerMqttAdapter extends AuctionEventMqttListener{ private static final Logger LOGGER = LogManager.getLogger(ExternalAuctionStartedEventListenerMqttAdapter.class); - String auctionHouseURI = "http://tapas-auction-house.86-119-35-40.nip.io/"; + String auctionHouseURI = "https://tapas-auction-house.86-119-35-40.nip.io/"; - String taskListURI = "http://tapas-tasks.86-119-35-40.nip.io"; + String taskListURI = "https://tapas-tasks.86-119-35-40.nip.io"; @Override public boolean handleEvent(MqttMessage message){ diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java index 2178fb4..9dc1508 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java @@ -61,6 +61,7 @@ public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { String body = TaskJsonRepresentation.serialize(task); LOGGER.info(body); var postURI = URI.create(bidderAuctionHouseUri + "/taskwinner"); + LOGGER.info(postURI); HttpRequest postRequest = HttpRequest.newBuilder() .uri(postURI) .header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE) -- 2.45.1 From 3141f97f41a819c2333b54150098c8a5d8bb8b9d Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 23:08:22 +0100 Subject: [PATCH 92/94] smaller fixes --- .../adapter/out/web/AuctionWonEventHttpAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java index 9dc1508..ec04b2b 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/web/AuctionWonEventHttpAdapter.java @@ -68,7 +68,7 @@ public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); - var postResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); + var postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString()); LOGGER.info(postResponse.statusCode()); } catch (IOException e) { -- 2.45.1 From 53d815b93d7c1e2b1e6a840d748524c44ecd2d9a Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 23:26:02 +0100 Subject: [PATCH 93/94] smallest fixes --- .../auctionhouse/adapter/in/web/WinningBidWebController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java index 9d252b5..e4c5aa1 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/web/WinningBidWebController.java @@ -35,7 +35,7 @@ public class WinningBidWebController { try { var body = payload.serialize(); LOGGER.info(body); - var postURI = URI.create(taskListURI + "/tasks/"); + var postURI = URI.create("https://tapas-tasks.86-119-35-40.nip.io/tasks/"); HttpRequest postRequest = HttpRequest.newBuilder() .uri(postURI) .header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE) -- 2.45.1 From 1bd387413e8b64b9b28958931674705bcf5fdb01 Mon Sep 17 00:00:00 2001 From: reynisson Date: Sat, 20 Nov 2021 23:44:47 +0100 Subject: [PATCH 94/94] fixed Content-type header in external patch --- .../tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java index 8e6f106..4e18349 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/ExternalTaskExecutedWebAdapter.java @@ -55,7 +55,7 @@ public class ExternalTaskExecutedWebAdapter implements ExternalTaskExecutedEvent HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(externalTaskExecutedEvent.getOriginalTaskUri().getValue())) - .header("Content-Type", "application/json") + .header("Content-Type", TaskJsonPatchRepresentation.MEDIA_TYPE) .method("PATCH", HttpRequest.BodyPublishers.ofString(body)) .build(); -- 2.45.1

bCQZLZ=*V@|Ixd#LUbR@*~Do7Z_%{wu1!PRSf$F5|2pu#yN-BwGQ zAI?$^;$ik{+^=%}(`q-`y|l-b>$ZLt%{X6^L;qh|b4R?efGI9t}G@!Z0ip>Ew_ntSfE-ZeDAu$C)H zNlA)dG3_P;#>PM>!;SH)gFaA-dgGEPA9;jN#kkq@_=SteP?+=iqm4InF#gNFZ`4+! zC7L-s{97M%2G^!L+ty5!GSi$0M(3G^aYJ_!K`{1lVwSq6*b^)=natwXSh0y$j!QWE zBE;d|Ul4}@5qK1QZSKu6^gY4U8s?=|+w{2m{=^`9LHmX0fzy~;VCdxZYE%JxvAJ9B^S`_pfPU04N(x9Roo*SaZRffA`C zvWWbSN|4K%&ug7otfhXKOZb!~&E-62LH4iQEI4cGeH9J$c_XMfW6YN7-#g>ezhzvV z*0|Ds=IOpa{YGL|u(mm=GY1%8!I(D9MQ3)r$*^M@xPqW-l+1R6XS*aR%-n2mqPRZ_ z_ze82{{UvMP<9Pbxx_L}Q;gj5=Um2HrX9BXe{z6KSo>|tcSQLnv)}PY7dHksI%Iqj z*;M)TR~BJSc_xnh|0jCE+_XyU2&ThcWsYf+Je^=s~tk =#z6G7eW-KM>%~;6jC2g|I9u4& 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 zWId=eE{)json 20210307 + + + ch.unisg + common + 0.0.1-SNAPSHOT + diff --git a/executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java b/executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java deleted file mode 100644 index 9bd3fa5..0000000 --- a/executor-base/src/main/java/ch/unisg/executorBase/Executor1Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package ch.unisg.executorBase; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Executor1Application { - - public static void main(String[] args) { - SpringApplication.run(Executor1Application.class, args); - } - -} diff --git a/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java b/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java deleted file mode 100644 index 5119ac5..0000000 --- a/executor-base/src/main/java/ch/unisg/executorBase/common/SelfValidating.java +++ /dev/null @@ -1,30 +0,0 @@ -package ch.unisg.executorBase.common; - -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; -import java.util.Set; - -public class SelfValidating { - - private Validator validator; - - public SelfValidating() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); - validator = factory.getValidator(); - } - - /** - * Evaluates all Bean Validations on the attributes of this - * instance. - */ - protected void validateSelf() { - @SuppressWarnings("unchecked") - Set> violations = validator.validate((T) this); - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } - } -} diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java index 182f2ba..6c1c659 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.adapter.in.web; +package ch.unisg.executorbase.executor.adapter.in.web; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -7,9 +7,9 @@ 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; +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 { diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 971e583..0947e4f 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.adapter.out.web; +package ch.unisg.executorbase.executor.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -9,13 +9,15 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; -import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort; -import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; +import ch.unisg.executorbase.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent; public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { - String server = "http://127.0.0.1:8082"; + @Value("${roster.url}") + String server; Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName()); @@ -37,10 +39,11 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort try { client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } System.out.println("Finish execution event sent with result:" + event.getResult()); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index 05852fa..14976f2 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.adapter.out.web; +package ch.unisg.executorbase.executor.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -8,12 +8,14 @@ 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.executorBase.executor.application.port.out.GetAssignmentPort; -import ch.unisg.executorBase.executor.domain.ExecutorType; -import ch.unisg.executorBase.executor.domain.Task; +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; @@ -21,17 +23,17 @@ import org.json.JSONObject; @Primary public class GetAssignmentAdapter implements GetAssignmentPort { - String server = "http://127.0.0.1:8082"; + @Value("${roster.url}") + String server; Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName()); @Override - public Task getAssignment(ExecutorType executorType, String ip, int port) { + public Task getAssignment(ExecutorType executorType, ExecutorURI executorURI) { String body = new JSONObject() .put("executorType", executorType) - .put("ip", ip) - .put("port", port) + .put("executorURI", executorURI.getValue()) .toString(); HttpClient client = HttpClient.newHttpClient(); @@ -49,10 +51,11 @@ public class GetAssignmentAdapter implements GetAssignmentPort { return new Task(new JSONObject(response.body()).getString("taskID")); - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } return null; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index 720b015..cad09a9 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.adapter.out.web; +package ch.unisg.executorbase.executor.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -9,28 +9,30 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONObject; +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.application.port.out.NotifyExecutorPoolPort; -import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.executorbase.executor.application.port.out.NotifyExecutorPoolPort; +import ch.unisg.executorbase.executor.domain.ExecutorType; @Component @Primary public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { - String server = "http://127.0.0.1:8083"; + @Value("${executor-pool.url}") + String server; Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); @Override - public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { + public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) { String body = new JSONObject() .put("executorTaskType", executorType) - .put("executorIp", ip) - .put("executorPort", Integer.toString(port)) + .put("executorURI", executorURI.getValue()) .toString(); HttpClient client = HttpClient.newHttpClient(); @@ -45,10 +47,11 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { if (response.statusCode() == HttpStatus.CREATED.value()) { return true; } - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); - // Restore interrupted state... Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } return false; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java index cfa32bb..57bee60 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableCommand.java @@ -1,10 +1,9 @@ -package ch.unisg.executorBase.executor.application.port.in; - -import ch.unisg.executorBase.common.SelfValidating; -import ch.unisg.executorBase.executor.domain.ExecutorType; +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 diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java index cc5215f..5e000da 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/in/TaskAvailableUseCase.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.application.port.in; +package ch.unisg.executorbase.executor.application.port.in; public interface TaskAvailableUseCase { void newTaskAvailable(TaskAvailableCommand command); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java index 1bf668e..ef65922 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/ExecutionFinishedEventPort.java @@ -1,6 +1,6 @@ -package ch.unisg.executorBase.executor.application.port.out; +package ch.unisg.executorbase.executor.application.port.out; -import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; +import ch.unisg.executorbase.executor.domain.ExecutionFinishedEvent; public interface ExecutionFinishedEventPort { void publishExecutionFinishedEvent(ExecutionFinishedEvent event); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java index 79d3a0a..95dc15d 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/GetAssignmentPort.java @@ -1,8 +1,9 @@ -package ch.unisg.executorBase.executor.application.port.out; +package ch.unisg.executorbase.executor.application.port.out; -import ch.unisg.executorBase.executor.domain.ExecutorType; -import ch.unisg.executorBase.executor.domain.Task; +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, String ip, int port); + Task getAssignment(ExecutorType executorType, ExecutorURI executorURI); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java index 6d41ab4..1d4d3d3 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/port/out/NotifyExecutorPoolPort.java @@ -1,7 +1,8 @@ -package ch.unisg.executorBase.executor.application.port.out; +package ch.unisg.executorbase.executor.application.port.out; -import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.executorbase.executor.domain.ExecutorType; public interface NotifyExecutorPoolPort { - boolean notifyExecutorPool(String ip, int port, ExecutorType executorType); + boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java index a5ccb64..aee3142 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/NotifyExecutorPoolService.java @@ -1,7 +1,8 @@ -package ch.unisg.executorBase.executor.application.service; +package ch.unisg.executorbase.executor.application.service; -import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; -import ch.unisg.executorBase.executor.domain.ExecutorType; +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 @@ -9,7 +10,7 @@ public class NotifyExecutorPoolService { private final NotifyExecutorPoolPort notifyExecutorPoolPort; - public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { - return notifyExecutorPoolPort.notifyExecutorPool(ip, port, executorType); + public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) { + return notifyExecutorPoolPort.notifyExecutorPool(executorURI, executorType); } } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java index a4f5e6e..c770985 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java @@ -1,9 +1,9 @@ -package ch.unisg.executorBase.executor.application.service; +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 ch.unisg.executorbase.executor.application.port.in.TaskAvailableCommand; +import ch.unisg.executorbase.executor.application.port.in.TaskAvailableUseCase; import lombok.RequiredArgsConstructor; import javax.transaction.Transactional; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java index 31fd0e6..fea6102 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutionFinishedEvent.java @@ -1,12 +1,12 @@ -package ch.unisg.executorBase.executor.domain; +package ch.unisg.executorbase.executor.domain; import lombok.Getter; public class ExecutionFinishedEvent { - + @Getter private String taskID; - + @Getter private String result; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index c9df1a8..7644887 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -1,26 +1,24 @@ -package ch.unisg.executorBase.executor.domain; +package ch.unisg.executorbase.executor.domain; -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.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.service.NotifyExecutorPoolService; +import ch.unisg.common.exception.InvalidExecutorURIException; +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 String ip; + private ExecutorURI executorURI; @Getter private ExecutorType executorType; - @Getter - private int port; - @Getter private ExecutorStatus status; @@ -34,12 +32,17 @@ public abstract class ExecutorBase { public ExecutorBase(ExecutorType executorType) { System.out.println("Starting Executor"); // TODO set this automaticly - this.ip = "localhost"; - this.port = 8084; + try { + this.executorURI = new ExecutorURI("localhost:8084"); + } catch (InvalidExecutorURIException e) { + // Shutdown system if ip or port is not valid + System.exit(1); + } + this.executorType = executorType; this.status = ExecutorStatus.STARTING_UP; - if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { + if(!notifyExecutorPoolService.notifyExecutorPool(this.executorURI, this.executorType)) { System.exit(0); } else { this.status = ExecutorStatus.IDLING; @@ -48,8 +51,7 @@ public abstract class ExecutorBase { } public void getAssignment() { - Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getIp(), - this.getPort()); + Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getExecutorURI()); if (newTask != null) { this.executeTask(newTask); } else { diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java index 8bd5bc3..d65412e 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.domain; +package ch.unisg.executorbase.executor.domain; public enum ExecutorStatus { STARTING_UP, diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java index 0b2b305..a8bc0e1 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.domain; +package ch.unisg.executorbase.executor.domain; public enum ExecutorType { ADDITION, ROBOT; diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java index fec330f..0a2164f 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java @@ -1,4 +1,4 @@ -package ch.unisg.executorBase.executor.domain; +package ch.unisg.executorbase.executor.domain; import lombok.Getter; import lombok.Setter; diff --git a/executor-base/src/main/resources/application.properties b/executor-base/src/main/resources/application.properties index 4d360de..3eee96a 100644 --- a/executor-base/src/main/resources/application.properties +++ b/executor-base/src/main/resources/application.properties @@ -1 +1,6 @@ server.port=8081 +roster.url=http://127.0.0.1:8082 +executor-pool.url=http://127.0.0.1:8083 +executor1.url=http://127.0.0.1:8084 +executor2.url=http://127.0.0.1:8085 +task-list.url=http://127.0.0.1:8081 diff --git a/executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java b/executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java deleted file mode 100644 index 6fec034..0000000 --- a/executor-base/src/test/java/ch/unisg/executorBase/Executor1ApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package ch.unisg.executorBase; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class executorBaseApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java index 1f08545..a0e4f75 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -9,9 +9,9 @@ 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; +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 { diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java index 5b011c1..35bbb93 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java @@ -1,6 +1,6 @@ package ch.unisg.executor1.executor.application.port.out; -import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.executorbase.executor.domain.ExecutorType; public interface UserToRobotPort { String userToRobot(); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java index d502053..c709fcf 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -3,9 +3,9 @@ package ch.unisg.executor1.executor.application.service; import org.springframework.stereotype.Component; import ch.unisg.executor1.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 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; @@ -20,7 +20,7 @@ public class TaskAvailableService implements TaskAvailableUseCase { Executor executor = Executor.getExecutor(); - if (executor.getExecutorType() == command.getTaskType() && + if (executor.getExecutorType() == command.getTaskType() && executor.getStatus() == ExecutorStatus.IDLING) { executor.getAssignment(); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java index cc11e64..84b576a 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -10,8 +10,8 @@ import ch.unisg.executor1.executor.adapter.out.UserToRobotAdapter; import ch.unisg.executor1.executor.application.port.out.DeleteUserFromRobotPort; import ch.unisg.executor1.executor.application.port.out.InstructionToRobotPort; import ch.unisg.executor1.executor.application.port.out.UserToRobotPort; -import ch.unisg.executorBase.executor.domain.ExecutorBase; -import ch.unisg.executorBase.executor.domain.ExecutorType; +import ch.unisg.executorbase.executor.domain.ExecutorBase; +import ch.unisg.executorbase.executor.domain.ExecutorType; public class Executor extends ExecutorBase { diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java index a14b58f..bf53c24 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java @@ -9,9 +9,9 @@ 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; +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 { diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 6fa918d..49b8e70 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -3,9 +3,9 @@ package ch.unisg.executor2.executor.application.service; import org.springframework.stereotype.Component; import ch.unisg.executor2.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 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; diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index 4d022b5..0708434 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -1,8 +1,9 @@ package ch.unisg.executor2.executor.domain; import java.util.concurrent.TimeUnit; -import ch.unisg.executorBase.executor.domain.ExecutorBase; -import ch.unisg.executorBase.executor.domain.ExecutorType; + +import ch.unisg.executorbase.executor.domain.ExecutorBase; +import ch.unisg.executorbase.executor.domain.ExecutorType; public class Executor extends ExecutorBase { -- 2.45.1 From 3184ab3389e14d20a5a26d11fb6f0b504b45935f Mon Sep 17 00:00:00 2001 From: "julius.lautz" Date: Mon, 1 Nov 2021 22:07:21 +0100 Subject: [PATCH 38/94] cleaned up task list + started implementation of deleteTask --- tapas-tasks/pom.xml | 6 ++ .../in/formats/TaskJsonRepresentation.java | 2 +- .../in/web/CompleteTaskWebController.java | 12 ++- .../in/web/DeleteTaskWebController.java | 15 ++-- .../in/web/TaskAssignedWebController.java | 12 ++- .../tasks/adapter/in/web/TaskMediaType.java | 23 ------ .../out/web/CanTaskBeDeletedWebAdapter.java | 59 +++++++++++++++ .../PublishNewTaskAddedEventWebAdapter.java | 2 +- .../port/in/DeleteTaskCommand.java | 7 +- .../port/out/CanTaskBeDeletedPort.java | 7 ++ .../service/CompleteTaskService.java | 6 +- .../service/DeleteTaskService.java | 11 ++- .../service/TaskAssignedService.java | 2 +- .../tasks/domain/DeleteTaskEvent.java | 11 +++ .../unisg/tapastasks/tasks/domain/Task.java | 35 ++++----- .../tapastasks/tasks/domain/TaskList.java | 75 ++++++++++--------- 16 files changed, 178 insertions(+), 107 deletions(-) delete mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/CanTaskBeDeletedPort.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/DeleteTaskEvent.java diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 0118cf9..a815cef 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -75,6 +75,12 @@ org.eclipse.paho.client.mqttv3 1.2.0 + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + compile + diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java index eb89415..ff8158a 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java @@ -88,7 +88,7 @@ final public class TaskJsonRepresentation { this.taskId = task.getTaskId().getValue(); this.taskStatus = task.getTaskStatus().getValue().name(); - this.originalTaskUri = (task.getOriginalTaskUri() == null) ? + this.originalTaskUri = (task. getOriginalTaskUri() == null) ? null : task.getOriginalTaskUri().getValue(); this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue(); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java index 536b72c..ec2b7b0 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/CompleteTaskWebController.java @@ -1,8 +1,10 @@ package ch.unisg.tapastasks.tasks.adapter.in.web; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskCommand; import ch.unisg.tapastasks.tasks.application.port.in.CompleteTaskUseCase; import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -21,7 +23,7 @@ public class CompleteTaskWebController { this.completeTaskUseCase = completeTaskUseCase; } - @PostMapping(path = "/tasks/completeTask", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) + @PostMapping(path = "/tasks/completeTask", consumes = {TaskJsonRepresentation.MEDIA_TYPE}) public ResponseEntity completeTask (@RequestBody Task task){ try { @@ -32,10 +34,12 @@ public class CompleteTaskWebController { Task updateATask = completeTaskUseCase.completeTask(command); HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); - return new ResponseEntity<>(TaskMediaType.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); - } catch(ConstraintViolationException e){ + return new ResponseEntity<>(TaskJsonRepresentation.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (ConstraintViolationException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java index af721d1..ef79e6a 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/DeleteTaskWebController.java @@ -1,9 +1,11 @@ package ch.unisg.tapastasks.tasks.adapter.in.web; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskCommand; import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskUseCase; import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -23,26 +25,27 @@ public class DeleteTaskWebController { this.deleteClassUseCase = deleteClassUseCase; } - @PostMapping(path="/tasks/deleteTask", consumes = {TaskMediaType.TASK_MEDIA_TYPE}) + @PostMapping(path="/tasks/deleteTask", consumes = {TaskJsonRepresentation.MEDIA_TYPE}) public ResponseEntity deleteTask (@RequestBody Task task){ try { - DeleteTaskCommand command = new DeleteTaskCommand(task.getTaskId()); + DeleteTaskCommand command = new DeleteTaskCommand(task.getTaskId(), task.getOriginalTaskUri()); Optional deleteATask = deleteClassUseCase.deleteTask(command); // Check if the task with the given identifier exists if (deleteATask.isEmpty()) { - // If not, through a 404 Not Found status code throw new ResponseStatusException(HttpStatus.NOT_FOUND); } HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); - return new ResponseEntity<>(TaskMediaType.serialize(deleteATask.get()), responseHeaders, HttpStatus.ACCEPTED); - } catch(ConstraintViolationException e){ + return new ResponseEntity<>(TaskJsonRepresentation.serialize(deleteATask.get()), responseHeaders, HttpStatus.ACCEPTED); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (ConstraintViolationException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java index 9dfa6a2..b58d159 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskAssignedWebController.java @@ -1,8 +1,10 @@ package ch.unisg.tapastasks.tasks.adapter.in.web; +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedCommand; import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedUseCase; import ch.unisg.tapastasks.tasks.domain.Task; +import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -21,7 +23,7 @@ public class TaskAssignedWebController { this.taskAssignedUseCase = taskAssignedUseCase; } - @PostMapping(path="/tasks/assignTask", consumes= {TaskMediaType.TASK_MEDIA_TYPE}) + @PostMapping(path="/tasks/assignTask", consumes= {TaskJsonRepresentation.MEDIA_TYPE}) public ResponseEntity assignTask(@RequestBody Task task){ try{ TaskAssignedCommand command = new TaskAssignedCommand( @@ -31,10 +33,12 @@ public class TaskAssignedWebController { Task updateATask = taskAssignedUseCase.assignTask(command); HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE); - return new ResponseEntity<>(TaskMediaType.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); - } catch (ConstraintViolationException e){ + return new ResponseEntity<>(TaskJsonRepresentation.serialize(updateATask), responseHeaders, HttpStatus.ACCEPTED); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (ConstraintViolationException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java deleted file mode 100644 index d9a0a46..0000000 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/web/TaskMediaType.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.unisg.tapastasks.tasks.adapter.in.web; - -import ch.unisg.tapastasks.tasks.domain.Task; -import ch.unisg.tapastasks.tasks.domain.TaskList; -import org.json.JSONObject; - -final public class TaskMediaType { - public static final String TASK_MEDIA_TYPE = "application/json"; - - public static String serialize(Task task) { - JSONObject payload = new JSONObject(); - - payload.put("taskId", task.getTaskId().getValue()); - payload.put("taskName", task.getTaskName().getValue()); - payload.put("taskType", task.getTaskType().getValue()); - payload.put("taskState", task.getTaskState().getValue()); - payload.put("taskListName", TaskList.getTapasTaskList().getTaskListName().getValue()); - payload.put("taskResult", task.getTaskResult().getValue()); - return payload.toString(); - } - - private TaskMediaType() { } -} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java new file mode 100644 index 0000000..5061e3d --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/CanTaskBeDeletedWebAdapter.java @@ -0,0 +1,59 @@ +package ch.unisg.tapastasks.tasks.adapter.out.web; + + +import ch.unisg.tapastasks.tasks.application.port.out.CanTaskBeDeletedPort; +import ch.unisg.tapastasks.tasks.domain.DeleteTaskEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +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.HashMap; + +@Component +@Primary +public class CanTaskBeDeletedWebAdapter implements CanTaskBeDeletedPort { + + // Base URI of the service interested in this event + //Todo: Add the right IP address + String server = null; + + @Override + public void canTaskBeDeletedEvent(DeleteTaskEvent event){ + + var values = new HashMap<> () {{ + put("taskId", event.taskId); + put("taskUri", event.taskUri); + }}; + + var objectMapper = new ObjectMapper(); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(values); + } catch (JsonProcessingException e){ + e.printStackTrace(); + } + + //Todo: Question: How do we include the URI from the DeleteTaskEvent? Do we even need it? + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"task")) + .header("Content-Type", "application/task+json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + //Todo: The following parameters probably need to be changed to get the right error code + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e){ + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java index d642eca..569b1e9 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/web/PublishNewTaskAddedEventWebAdapter.java @@ -42,7 +42,7 @@ public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(server+"/task")) - .header("Content-Type", "application/json") + .header("Content-Type", "application/task+json") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java index 24acbb8..b57c719 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/in/DeleteTaskCommand.java @@ -2,6 +2,7 @@ package ch.unisg.tapastasks.tasks.application.port.in; import ch.unisg.tapastasks.common.SelfValidating; import ch.unisg.tapastasks.tasks.domain.Task.TaskId; +import ch.unisg.tapastasks.tasks.domain.Task.OriginalTaskUri; import lombok.Value; import javax.validation.constraints.NotNull; @@ -11,8 +12,12 @@ public class DeleteTaskCommand extends SelfValidating { @NotNull private final TaskId taskId; - public DeleteTaskCommand(TaskId taskId){ + @NotNull + private final OriginalTaskUri taskUri; + + public DeleteTaskCommand(TaskId taskId, OriginalTaskUri taskUri){ this.taskId=taskId; + this.taskUri = taskUri; this.validateSelf(); } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/CanTaskBeDeletedPort.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/CanTaskBeDeletedPort.java new file mode 100644 index 0000000..67bde16 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/CanTaskBeDeletedPort.java @@ -0,0 +1,7 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.DeleteTaskEvent; + +public interface CanTaskBeDeletedPort { + void canTaskBeDeletedEvent(DeleteTaskEvent event); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java index bade832..0e7f817 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/CompleteTaskService.java @@ -19,17 +19,13 @@ public class CompleteTaskService implements CompleteTaskUseCase { @Override public Task completeTask(CompleteTaskCommand command){ - // TODO Retrieve the task based on ID TaskList taskList = TaskList.getTapasTaskList(); Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); - // TODO Update the status and result (and save?) Task newTask = updatedTask.get(); newTask.taskResult = new TaskResult(command.getTaskResult().getValue()); - newTask.taskState = new TaskState(Task.State.EXECUTED); + newTask.taskStatus = new TaskStatus(Task.Status.EXECUTED); - - // TODO return the updated task return newTask; } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java index cfebcd6..f865f4c 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java @@ -19,10 +19,15 @@ public class DeleteTaskService implements DeleteTaskUseCase { @Override public Optional deleteTask(DeleteTaskCommand command){ - // TODO check with assignment service if we can delte - TaskList taskList = TaskList.getTapasTaskList(); - return taskList.deleteTaskById(command.getTaskId()); + Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); + Task newTask = updatedTask.get(); + // TODO: Fill in the right condition into the if-statement and the else-statement + if (/*the task can be deleted*/){ + return taskList.deleteTaskById(command.getTaskId()); + } else { + /*send message back to TaskList that the task cannot be deleted*/ + } } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java index baa6059..6c580e4 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/TaskAssignedService.java @@ -24,7 +24,7 @@ public class TaskAssignedService implements TaskAssignedUseCase { // update the status to assigned Task updatedTask = task.get(); - updatedTask.taskState = new TaskState(State.ASSIGNED); + updatedTask.taskStatus = new TaskStatus(Status.ASSIGNED); return updatedTask; } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/DeleteTaskEvent.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/DeleteTaskEvent.java new file mode 100644 index 0000000..16e803b --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/DeleteTaskEvent.java @@ -0,0 +1,11 @@ +package ch.unisg.tapastasks.tasks.domain; + +public class DeleteTaskEvent { + public String taskId; + public String taskUri; + + public DeleteTaskEvent(String taskId, String taskUri){ + this.taskId = taskId; + this.taskUri = taskUri; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index ebe9d1c..4444beb 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -21,40 +21,33 @@ public class Task { @Getter private final TaskType taskType; - @Getter - public TaskState taskState; // had to make public for CompleteTaskService + @Getter @Setter + public TaskStatus taskStatus; // had to make public for CompleteTaskService @Getter public TaskResult taskResult; // same as above - // private final OriginalTaskUri originalTaskUri; + @Getter + private final OriginalTaskUri originalTaskUri; - // @Getter @Setter - // private TaskStatus taskStatus; + @Getter @Setter + private ServiceProvider provider; - // @Getter @Setter - // private ServiceProvider provider; + @Getter @Setter + private InputData inputData; - // @Getter @Setter - // private InputData inputData; - - // @Getter @Setter - // private OutputData outputData; + @Getter @Setter + private OutputData outputData; public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) { - this.taskId = new TaskId(UUID.randomUUID().toString()); - this.taskName = taskName; this.taskType = taskType; - this.taskState = new TaskState(State.OPEN); + this.taskStatus = new TaskStatus(Status.OPEN); this.taskId = new TaskId(UUID.randomUUID().toString()); this.taskResult = new TaskResult(""); - // this.originalTaskUri = taskUri; - - // this.taskStatus = new TaskStatus(Status.OPEN); - - // this.inputData = null; - // this.outputData = null; + this.originalTaskUri = taskUri; + this.inputData = null; + this.outputData = null; } protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java index 7a4e70f..e07bcd8 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/TaskList.java @@ -67,50 +67,51 @@ public class TaskList { } public Optional deleteTaskById(Task.TaskId id) { - for (Task task: listOfTasks.value){ - if(task.getTaskId().getValue().equalsIgnoreCase(id.getValue())){ + for (Task task : listOfTasks.value) { + if (task.getTaskId().getValue().equalsIgnoreCase(id.getValue())) { listOfTasks.value.remove(task); return Optional.of(task); } } return Optional.empty(); - // public Task changeTaskStatusToAssigned(Task.TaskId id, Optional serviceProvider) - // throws TaskNotFoundException { - // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.ASSIGNED), serviceProvider, Optional.empty()); - // } - - // public Task changeTaskStatusToRunning(Task.TaskId id, Optional serviceProvider) - // throws TaskNotFoundException { - // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.RUNNING), serviceProvider, Optional.empty()); - // } - - // public Task changeTaskStatusToExecuted(Task.TaskId id, Optional serviceProvider, - // Optional outputData) throws TaskNotFoundException { - // return changeTaskStatus(id, new Task.TaskStatus(Task.Status.EXECUTED), serviceProvider, outputData); - // } - - // private Task changeTaskStatus(Task.TaskId id, Task.TaskStatus status, Optional serviceProvider, - // Optional outputData) { - // Optional taskOpt = retrieveTaskById(id); - - // if (taskOpt.isEmpty()) { - // throw new TaskNotFoundException(); - // } - - // Task task = taskOpt.get(); - // task.setTaskStatus(status); - - // if (serviceProvider.isPresent()) { - // task.setProvider(serviceProvider.get()); - // } - - // if (outputData.isPresent()) { - // task.setOutputData(outputData.get()); - // } - - // return task; } + public Task changeTaskStatusToAssigned(Task.TaskId id, Optional serviceProvider) + throws TaskNotFoundException { + return changeTaskStatus(id, new Task.TaskStatus(Task.Status.ASSIGNED), serviceProvider, Optional.empty()); + } + + public Task changeTaskStatusToRunning(Task.TaskId id, Optional serviceProvider) + throws TaskNotFoundException { + return changeTaskStatus(id, new Task.TaskStatus(Task.Status.RUNNING), serviceProvider, Optional.empty()); + } + + public Task changeTaskStatusToExecuted(Task.TaskId id, Optional serviceProvider, + Optional outputData) throws TaskNotFoundException { + return changeTaskStatus(id, new Task.TaskStatus(Task.Status.EXECUTED), serviceProvider, outputData); + } + + private Task changeTaskStatus(Task.TaskId id, Task.TaskStatus status, Optional serviceProvider, + Optional outputData) { + Optional taskOpt = retrieveTaskById(id); + + if (taskOpt.isEmpty()) { + throw new TaskNotFoundException(); + } + + Task task = taskOpt.get(); + task.setTaskStatus(status); + + if (serviceProvider.isPresent()) { + task.setProvider(serviceProvider.get()); + } + + if (outputData.isPresent()) { + task.setOutputData(outputData.get()); + } + + return task; + } @Value public static class TaskListName { -- 2.45.1 From 7af2b6df66cf177e75c441fbf0abaaae6ad10b10 Mon Sep 17 00:00:00 2001 From: "julius.lautz" Date: Mon, 1 Nov 2021 22:18:00 +0100 Subject: [PATCH 39/94] cleaned up task list + started implementation of deleteTask --- .../tasks/adapter/in/formats/TaskJsonRepresentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java index ff8158a..eb89415 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/in/formats/TaskJsonRepresentation.java @@ -88,7 +88,7 @@ final public class TaskJsonRepresentation { this.taskId = task.getTaskId().getValue(); this.taskStatus = task.getTaskStatus().getValue().name(); - this.originalTaskUri = (task. getOriginalTaskUri() == null) ? + this.originalTaskUri = (task.getOriginalTaskUri() == null) ? null : task.getOriginalTaskUri().getValue(); this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue(); -- 2.45.1 From 06172b34cda556ee53b0c7564cf29ce314e83ac4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 2 Nov 2021 20:58:59 +0100 Subject: [PATCH 40/94] code improvements & delete task endpoint --- .../in/web/ApplyForTaskController.java | 5 ++- .../adapter/in/web/DeleteTaskController.java | 35 +++++++++++++++++++ .../adapter/in/web/NewTaskController.java | 6 +++- .../in/web/TaskCompletedController.java | 4 +++ .../in/web/WebControllerExceptionHandler.java | 4 +++ ...llExecutorInExecutorPoolByTypeAdapter.java | 5 +++ .../out/web/PublishNewTaskEventAdapter.java | 4 +++ .../web/PublishTaskAssignedEventAdapter.java | 4 +++ .../web/PublishTaskCompletedEventAdapter.java | 4 +++ .../port/in/DeleteTaskCommand.java | 24 +++++++++++++ .../port/in/DeleteTaskUseCase.java | 5 +++ ...etAllExecutorInExecutorPoolByTypePort.java | 4 +++ .../port/out/NewTaskEventPort.java | 4 +++ .../port/out/TaskAssignedEventPort.java | 4 +++ .../port/out/TaskCompletedEventPort.java | 4 +++ .../service/ApplyForTaskService.java | 5 +++ .../service/DeleteTaskService.java | 27 ++++++++++++++ .../application/service/NewTaskService.java | 12 ++++--- .../service/TaskCompletedService.java | 4 +++ .../assignment/assignment/domain/Roster.java | 34 ++++++++++++++++++ .../unisg/common/valueobject/ExecutorURI.java | 13 ++++--- .../in/web/TaskAvailableController.java | 6 +++- .../web/ExecutionFinishedEventAdapter.java | 6 +++- .../adapter/out/web/GetAssignmentAdapter.java | 7 ++++ .../out/web/NotifyExecutorPoolAdapter.java | 4 +++ .../service/TaskAvailableService.java | 2 +- .../executor/domain/ExecutorBase.java | 34 +++++++++++++----- .../executor/domain/ExecutorStatus.java | 6 ++-- .../executor/domain/ExecutorType.java | 4 +++ 29 files changed, 256 insertions(+), 24 deletions(-) create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java create mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java index c77f6f9..7b8331c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java @@ -17,6 +17,10 @@ public class ApplyForTaskController { this.applyForTaskUseCase = applyForTaskUseCase; } + /** + * Checks if task is available for the requesting executor. + * @return a task or null if no task found + **/ @PostMapping(path = "/task/apply", consumes = {"application/json"}) public Task applyForTask(@RequestBody ExecutorInfo executorInfo) { @@ -24,6 +28,5 @@ public class ApplyForTaskController { executorInfo.getExecutorURI()); return applyForTaskUseCase.applyForTask(command); - } } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java new file mode 100644 index 0000000..b34e6db --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java @@ -0,0 +1,35 @@ +package ch.unisg.assignment.assignment.adapter.in.web; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import ch.unisg.assignment.assignment.application.port.in.DeleteTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.DeleteTaskUseCase; +import ch.unisg.assignment.assignment.domain.Task; + +@RestController +public class DeleteTaskController { + private final DeleteTaskUseCase deleteTaskUseCase; + + public DeleteTaskController(DeleteTaskUseCase deleteTaskUseCase) { + this.deleteTaskUseCase = deleteTaskUseCase; + } + + /** + * Controller to delete a task + * @return 200 OK, 409 Conflict + **/ + @DeleteMapping(path = "/task", consumes = {"application/task+json"}) + public ResponseEntity applyForTask(@RequestBody Task task) { + + DeleteTaskCommand command = new DeleteTaskCommand(task.getTaskID(), task.getTaskType()); + + if (deleteTaskUseCase.deleteTask(command)) { + return new ResponseEntity<>(HttpStatus.OK); + } + return new ResponseEntity<>(HttpStatus.CONFLICT); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java index 18bad8f..9faf2ec 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java @@ -18,7 +18,11 @@ public class NewTaskController { this.newTaskUseCase = newTaskUseCase; } - @PostMapping(path = "/task", consumes = {"application/json"}) + /** + * Controller which handles the new task event from the tasklist + * @return 201 Create or 409 Conflict + **/ + @PostMapping(path = "/task", consumes = {"application/task+json"}) public ResponseEntity newTaskController(@RequestBody Task task) { NewTaskCommand command = new NewTaskCommand(task.getTaskID(), task.getTaskType()); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java index cde4c0a..df89c7f 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java @@ -19,6 +19,10 @@ public class TaskCompletedController { this.taskCompletedUseCase = taskCompletedUseCase; } + /** + * Controller which handles the task completed event from executors + * @return 200 OK + **/ @PostMapping(path = "/task/completed", consumes = {"application/json"}) public ResponseEntity addNewTaskTaskToTaskList(@RequestBody Task task) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java index 99ad2a5..19cce0d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java @@ -11,6 +11,10 @@ import ch.unisg.common.exception.InvalidExecutorURIException; @ControllerAdvice public class WebControllerExceptionHandler { + /** + * Handles error of type InvalidExecutorURIException + * @return 404 Bad Request + **/ @ExceptionHandler(InvalidExecutorURIException.class) public ResponseEntity handleException(InvalidExecutorURIException e){ diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java index 0a91805..1c02839 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java @@ -24,6 +24,11 @@ public class GetAllExecutorInExecutorPoolByTypeAdapter implements GetAllExecutor @Value("${executor-pool.url}") private String server; + /** + * Requests all executor of the give type from the executor-pool and cheks if there is one + * avaialable of this type. + * @return Whether an executor exist or not + **/ @Override public boolean doesExecutorTypeExist(ExecutorType type) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java index db3de1c..10638d3 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java @@ -27,6 +27,10 @@ public class PublishNewTaskEventAdapter implements NewTaskEventPort { Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName()); + /** + * Informs executors about the availability of a new task. + * @return void + **/ @Override public void publishNewTaskEvent(NewTaskEvent event) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java index 209525e..45a10f3 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -25,6 +25,10 @@ public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort { Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName()); + /** + * Informs the task service about the assignment of the task. + * @return void + **/ @Override public void publishTaskAssignedEvent(TaskAssignedEvent event) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java index 6bd56a0..e9c4944 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -25,6 +25,10 @@ public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort Logger logger = Logger.getLogger(PublishTaskCompletedEventAdapter.class.getName()); + /** + * Informs the task service about the completion of the task. + * @return void + **/ @Override public void publishTaskCompleted(TaskCompletedEvent event) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java new file mode 100644 index 0000000..7239acc --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java @@ -0,0 +1,24 @@ +package ch.unisg.assignment.assignment.application.port.in; + +import javax.validation.constraints.NotNull; + +import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.common.validation.SelfValidating; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class DeleteTaskCommand extends SelfValidating { + @NotNull + private final String taskId; + + @NotNull + private final ExecutorType taskType; + + public DeleteTaskCommand(String taskId, ExecutorType taskType) { + this.taskId = taskId; + this.taskType = taskType; + this.validateSelf(); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java new file mode 100644 index 0000000..e890e8b --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java @@ -0,0 +1,5 @@ +package ch.unisg.assignment.assignment.application.port.in; + +public interface DeleteTaskUseCase { + boolean deleteTask(DeleteTaskCommand deleteTaskCommand); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java index e751727..9f6c824 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java @@ -3,6 +3,10 @@ package ch.unisg.assignment.assignment.application.port.out; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; public interface GetAllExecutorInExecutorPoolByTypePort { + /** + * Checks if a executor with the given type exist in our executor pool + * @return boolean + **/ boolean doesExecutorTypeExist(ExecutorType type); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java index 909a9ba..243c7f2 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java @@ -3,5 +3,9 @@ package ch.unisg.assignment.assignment.application.port.out; import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; public interface NewTaskEventPort { + /** + * Publishes the new task event. + * @return void + **/ void publishNewTaskEvent(NewTaskEvent event); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java index fefd4a1..5f55ec8 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java @@ -3,5 +3,9 @@ package ch.unisg.assignment.assignment.application.port.out; import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; public interface TaskAssignedEventPort { + /** + * Publishes the task assigned event. + * @return void + **/ void publishTaskAssignedEvent(TaskAssignedEvent taskAssignedEvent); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java index 43a8aa5..83ad179 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java @@ -3,5 +3,9 @@ package ch.unisg.assignment.assignment.application.port.out; import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; public interface TaskCompletedEventPort { + /** + * Publishes the task completed event. + * @return void + **/ void publishTaskCompleted(TaskCompletedEvent event); } diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java index 5ba1901..dfb70e0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java @@ -19,6 +19,11 @@ public class ApplyForTaskService implements ApplyForTaskUseCase { private final TaskAssignedEventPort taskAssignedEventPort; + /** + * Checks if a task is available and assignes it to the executor. If task got assigned a task + * assigned event gets published. + * @return assigned task or null if no task is found + **/ @Override public Task applyForTask(ApplyForTaskCommand command) { Task task = Roster.getInstance().assignTaskToExecutor(command.getTaskType(), diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java new file mode 100644 index 0000000..7d67e4a --- /dev/null +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java @@ -0,0 +1,27 @@ +package ch.unisg.assignment.assignment.application.service; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import ch.unisg.assignment.assignment.application.port.in.DeleteTaskCommand; +import ch.unisg.assignment.assignment.application.port.in.DeleteTaskUseCase; +import ch.unisg.assignment.assignment.domain.Roster; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +@Transactional +public class DeleteTaskService implements DeleteTaskUseCase { + + /** + * Check if task can get deleted + * @return if task can get deleted + **/ + @Override + public boolean deleteTask(DeleteTaskCommand command) { + Roster roster = Roster.getInstance(); + return roster.deleteTask(command.getTaskId(), command.getTaskType()); + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java index 8f60789..d240a4b 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java @@ -21,18 +21,22 @@ public class NewTaskService implements NewTaskUseCase { private final NewTaskEventPort newTaskEventPort; private final GetAllExecutorInExecutorPoolByTypePort getAllExecutorInExecutorPoolByTypePort; + /** + * Checks if we can execute the give task, if yes the task gets added to the task queue and return true. + * If the task can not be executed by an internal or auction house executor, the method return false. + * @return boolean + **/ @Override public boolean addNewTaskToQueue(NewTaskCommand command) { - if (!getAllExecutorInExecutorPoolByTypePort.doesExecutorTypeExist(command.getTaskType())) { - return false; - } + // if (!getAllExecutorInExecutorPoolByTypePort.doesExecutorTypeExist(command.getTaskType())) { + // return false; + // } Task task = new Task(command.getTaskID(), command.getTaskType()); Roster.getInstance().addTaskToQueue(task); - // TODO this event should be in the roster function xyz NewTaskEvent newTaskEvent = new NewTaskEvent(task.getTaskType()); newTaskEventPort.publishNewTaskEvent(newTaskEvent); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java index c8273ff..7c3e7f6 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java @@ -18,6 +18,10 @@ public class TaskCompletedService implements TaskCompletedUseCase { private final TaskCompletedEventPort taskCompletedEventPort; + /** + * Completes the task in the roster and publishes a task completed event. + * @return void + **/ @Override public void taskCompleted(TaskCompletedCommand command) { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java index 560d7fc..fb259c1 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java +++ b/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java @@ -3,6 +3,8 @@ package ch.unisg.assignment.assignment.domain; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; import ch.unisg.common.valueobject.ExecutorURI; @@ -11,25 +13,41 @@ public class Roster { private static final Roster roster = new Roster(); + // Queues which hold all the tasks which need to be assigned | Will be replaced by message queue later private HashMap> queues = new HashMap<>(); + // Roster witch holds information about which executor is assigned to a task private HashMap rosterMap = new HashMap<>(); + Logger logger = Logger.getLogger(Roster.class.getName()); + public static Roster getInstance() { return roster; } private Roster() {} + /** + * Adds a task to the task queue. + * @return void + * @see Task + **/ public void addTaskToQueue(Task task) { if (queues.containsKey(task.getTaskType().getValue())) { queues.get(task.getTaskType().getValue()).add(task); } else { queues.put(task.getTaskType().getValue(), new ArrayList<>(Arrays.asList(task))); } + logger.log(Level.INFO, "Added task with id {0} to queue", task.getTaskID()); } + /** + * Checks if a task of this type is in a queue and if so assignes it to the executor. + * @return assigned task or null if no task is found + * @see Task + **/ public Task assignTaskToExecutor(ExecutorType taskType, ExecutorURI executorURI) { + // TODO I don't think we need this if if (!queues.containsKey(taskType.getValue())) { return null; } @@ -45,8 +63,24 @@ public class Roster { return task; } + /** + * Removed a task from the roster after if got completed + * @return void + * @see Task + * @see Roster + **/ public void taskCompleted(String taskID) { rosterMap.remove(taskID); + logger.log(Level.INFO, "Task {0} completed", taskID); + } + + /** + * Deletes a task if it is still in the queue. + * @return Whether the task got deleted or not + **/ + public boolean deleteTask(String taskID, ExecutorType taskType) { + logger.log(Level.INFO, "Try to delete task with id {0}", taskID); + return queues.get(taskType.getValue()).removeIf(task -> task.getTaskID().equalsIgnoreCase(taskID)); } } diff --git a/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java index fc6b62d..a68a7e0 100644 --- a/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java +++ b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java @@ -8,11 +8,16 @@ public class ExecutorURI { private String value; public ExecutorURI(String uri) throws InvalidExecutorURIException { - if (uri.equalsIgnoreCase("localhost") || - uri.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { - this.value = uri; - } else { + String ip = uri.split(":")[0]; + int port = Integer.parseInt(uri.split(":")[1]); + // Check if valid ip4 + if (!ip.equalsIgnoreCase("localhost") && + !uri.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { + throw new InvalidExecutorURIException(); + // Check if valid port + } else if (port < 1024 || port > 65535) { throw new InvalidExecutorURIException(); } + this.value = uri; } } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java index 6c1c659..8fda5ac 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/in/web/TaskAvailableController.java @@ -19,7 +19,11 @@ public class TaskAvailableController { this.taskAvailableUseCase = taskAvailableUseCase; } - @GetMapping(path = "/newtask/{taskType}") + /** + * Controller for notification about new events. + * @return 200 OK + **/ + @GetMapping(path = "/newtask/{taskType}", consumes = { "application/json" }) public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { if (ExecutorType.contains(taskType.toUpperCase())) { diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java index 0947e4f..a5ae910 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/ExecutionFinishedEventAdapter.java @@ -21,6 +21,10 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName()); + /** + * Publishes the execution finished event + * @return void + **/ @Override public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { @@ -46,7 +50,7 @@ public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort logger.log(Level.SEVERE, e.getLocalizedMessage(), e); } - System.out.println("Finish execution event sent with result:" + event.getResult()); + logger.log(Level.INFO, "Finish execution event sent with result: {}", event.getResult()); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index 14976f2..ddcd550 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -28,6 +28,11 @@ public class GetAssignmentAdapter implements GetAssignmentPort { 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) { @@ -44,7 +49,9 @@ public class GetAssignmentAdapter implements GetAssignmentPort { .build(); try { + logger.info("Sending getAssignment Request"); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + logger.log(Level.INFO, "getAssignment request result:\n {}", response.body()); if (response.body().equals("")) { return null; } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java index cad09a9..2dba64f 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -27,6 +27,10 @@ public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { Logger logger = Logger.getLogger(NotifyExecutorPoolAdapter.class.getName()); + /** + * Notifies the executor-pool about the startup of this executor + * @return if the notification was successful + **/ @Override public boolean notifyExecutorPool(ExecutorURI executorURI, ExecutorType executorType) { diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java index c770985..050a807 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/application/service/TaskAvailableService.java @@ -15,6 +15,6 @@ public class TaskAvailableService implements TaskAvailableUseCase { @Override public void newTaskAvailable(TaskAvailableCommand command) { - // Placeholder so spring can create a bean + // Placeholder so spring can create a bean, implementation of this function is inside the executors } } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index 7644887..f0c5fa9 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -1,5 +1,7 @@ package ch.unisg.executorbase.executor.domain; +import java.util.logging.Logger; + import ch.unisg.common.exception.InvalidExecutorURIException; import ch.unisg.common.valueobject.ExecutorURI; import ch.unisg.executorbase.executor.adapter.out.web.ExecutionFinishedEventAdapter; @@ -23,25 +25,28 @@ public abstract class ExecutorBase { private ExecutorStatus status; // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model + // TODO I guess we can implement the execution as a service but there still is the problem with the startup request. // TODO I guess we can somehow autowire this but I don't know why it's not working :D 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(); - public ExecutorBase(ExecutorType executorType) { - System.out.println("Starting Executor"); + 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 try { this.executorURI = new ExecutorURI("localhost:8084"); } catch (InvalidExecutorURIException e) { - // Shutdown system if ip or port is not valid + // Shutdown system if the executorURI is not valid System.exit(1); } - this.executorType = executorType; - - this.status = ExecutorStatus.STARTING_UP; + // 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)) { System.exit(0); } else { @@ -50,6 +55,10 @@ public abstract class ExecutorBase { } } + /** + * Requests a new task from the task queue + * @return void + **/ public void getAssignment() { Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getExecutorURI()); if (newTask != null) { @@ -59,19 +68,28 @@ public abstract class ExecutorBase { } } + /** + * Start the execution of a task + * @return void + **/ private void executeTask(Task task) { - System.out.println("Starting execution"); + logger.info("Starting execution"); this.status = ExecutorStatus.EXECUTING; task.setResult(execution()); + // TODO implement logic if execution was not successful executionFinishedEventPort.publishExecutionFinishedEvent( new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); - System.out.println("Finish execution"); + logger.info("Finish execution"); getAssignment(); } + /** + * Implementation of the actual execution method of an executor + * @return the execution result + **/ protected abstract String execution(); } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java index d65412e..1fcf7de 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorStatus.java @@ -1,7 +1,7 @@ package ch.unisg.executorbase.executor.domain; public enum ExecutorStatus { - STARTING_UP, - EXECUTING, - IDLING, + STARTING_UP, // Executor is starting + EXECUTING, // Executor is currently executing a task + IDLING, // Executor has no tasks left and is waiting for new instructions } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java index a8bc0e1..ca9533a 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorType.java @@ -3,6 +3,10 @@ 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()) { -- 2.45.1 From dacb5605d7799aa93899edeb9fefa4fdc1564895 Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Tue, 2 Nov 2021 21:21:52 +0100 Subject: [PATCH 41/94] executor 2 change --- .../adapter/out/web/GetAssignmentAdapter.java | 4 ++-- .../executor/domain/ExecutorBase.java | 6 +++--- .../executorBase/executor/domain/Task.java | 6 +++++- .../executor2/executor/domain/Executor.java | 21 +++++++++++++------ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java index 05852fa..411073b 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/adapter/out/web/GetAssignmentAdapter.java @@ -46,8 +46,8 @@ public class GetAssignmentAdapter implements GetAssignmentPort { if (response.body().equals("")) { return null; } - - return new Task(new JSONObject(response.body()).getString("taskID")); + JSONObject responseBody = new JSONObject(response.body()); + return new Task(responseBody.getString("taskID"), responseBody.getString("input")); } catch (IOException | InterruptedException e) { logger.log(Level.SEVERE, e.getLocalizedMessage(), e); diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index c9df1a8..83ab862 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -61,7 +61,7 @@ public abstract class ExecutorBase { System.out.println("Starting execution"); this.status = ExecutorStatus.EXECUTING; - task.setResult(execution()); + task.setResult(execution(task.getInput())); executionFinishedEventPort.publishExecutionFinishedEvent( new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); @@ -70,6 +70,6 @@ public abstract class ExecutorBase { getAssignment(); } - protected abstract String execution(); - + protected abstract String execution(String... input); + } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java index fec330f..f455dcd 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/Task.java @@ -12,8 +12,12 @@ public class Task { @Setter private String result; - public Task(String taskID) { + @Getter + private String[] input; + + public Task(String taskID, String... input) { this.taskID = taskID; + this.input = input; } } diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index 4d022b5..f2021b8 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java +++ b/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -18,19 +18,28 @@ public class Executor extends ExecutorBase { @Override protected - String execution() { + String execution(String... input) { + + double result = Double.NaN; + int a = Integer.parseInt(input[0]); + int b = Integer.parseInt(input[2]); + String operation = input[1]; - int a = 20; - int b = 20; try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } - int result = a + b; + if (operation == "+") { + result = a + b; + } else if (operation == "*") { + result = a * b; + } else if (operation == "-") { + result = a - b; + } - return Integer.toString(result); + return Double.toString(result); } -} +} \ No newline at end of file -- 2.45.1 From 5606de7c26befb6a25fbd46d4ece686c4a2642f2 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 4 Nov 2021 17:23:49 +0100 Subject: [PATCH 42/94] Change type of ExecutorURI value to URI --- .../unisg/common/valueobject/ExecutorURI.java | 18 ++++-------------- .../executor/domain/ExecutorBase.java | 8 +------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java index a68a7e0..627104e 100644 --- a/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java +++ b/common/src/main/java/ch/unisg/common/valueobject/ExecutorURI.java @@ -1,23 +1,13 @@ package ch.unisg.common.valueobject; -import ch.unisg.common.exception.InvalidExecutorURIException; +import java.net.URI; import lombok.Value; @Value public class ExecutorURI { - private String value; + private URI value; - public ExecutorURI(String uri) throws InvalidExecutorURIException { - String ip = uri.split(":")[0]; - int port = Integer.parseInt(uri.split(":")[1]); - // Check if valid ip4 - if (!ip.equalsIgnoreCase("localhost") && - !uri.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) { - throw new InvalidExecutorURIException(); - // Check if valid port - } else if (port < 1024 || port > 65535) { - throw new InvalidExecutorURIException(); - } - this.value = uri; + public ExecutorURI(String uri) { + this.value = URI.create(uri); } } diff --git a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java index f0c5fa9..dddb89d 100644 --- a/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java +++ b/executor-base/src/main/java/ch/unisg/executorBase/executor/domain/ExecutorBase.java @@ -2,7 +2,6 @@ package ch.unisg.executorbase.executor.domain; import java.util.logging.Logger; -import ch.unisg.common.exception.InvalidExecutorURIException; import ch.unisg.common.valueobject.ExecutorURI; import ch.unisg.executorbase.executor.adapter.out.web.ExecutionFinishedEventAdapter; import ch.unisg.executorbase.executor.adapter.out.web.GetAssignmentAdapter; @@ -39,12 +38,7 @@ public abstract class ExecutorBase { this.status = ExecutorStatus.STARTING_UP; this.executorType = executorType; // TODO set this automaticly - try { - this.executorURI = new ExecutorURI("localhost:8084"); - } catch (InvalidExecutorURIException e) { - // Shutdown system if the executorURI is not valid - System.exit(1); - } + this.executorURI = new ExecutorURI("localhost:8084"); // 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)) { -- 2.45.1 From a96c8b2d236d4995c379d8c412526c03579447b9 Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Fri, 5 Nov 2021 11:32:22 +0100 Subject: [PATCH 43/94] renaming executors --- {executor1 => executorcomputation}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {executor1 => executorcomputation}/Dockerfile | 0 {executor1 => executorcomputation}/mvnw | 0 {executor1 => executorcomputation}/mvnw.cmd | 0 {executor2 => executorcomputation}/pom.xml | 2 +- .../executor2/ExecutorcomputationApplication.java | 8 ++++---- .../adapter/in/web/TaskAvailableController.java | 2 +- .../application/service/TaskAvailableService.java | 4 ++-- .../unisg/executor2/executor/domain/Executor.java | 2 +- .../src/main/resources/application.properties | 0 .../ExecutorcomputationApplicationTests.java | 4 ++-- {executor2 => executorrobot}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {executor2 => executorrobot}/Dockerfile | 0 {executor2 => executorrobot}/mvnw | 0 {executor2 => executorrobot}/mvnw.cmd | 0 {executor1 => executorrobot}/pom.xml | 2 +- .../unisg/executor1/ExecutorrobotApplication.java | 8 ++++---- .../adapter/in/web/TaskAvailableController.java | 2 +- .../adapter/out/DeleteUserFromRobotAdapter.java | 4 ++-- .../adapter/out/InstructionToRobotAdapter.java | 4 ++-- .../executor/adapter/out/UserToRobotAdapter.java | 4 ++-- .../port/out/DeleteUserFromRobotPort.java | 2 +- .../port/out/InstructionToRobotPort.java | 2 +- .../application/port/out/UserToRobotPort.java | 2 +- .../application/service/TaskAvailableService.java | 4 ++-- .../unisg/executor1/executor/domain/Executor.java | 14 +++++++------- .../src/main/resources/application.properties | 0 .../executor1/ExecutorrobotApplicationTests.java | 4 ++-- 34 files changed, 37 insertions(+), 37 deletions(-) rename {executor1 => executorcomputation}/.gitignore (100%) rename {executor1 => executorcomputation}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {executor1 => executorcomputation}/.mvn/wrapper/maven-wrapper.jar (100%) rename {executor1 => executorcomputation}/.mvn/wrapper/maven-wrapper.properties (100%) rename {executor1 => executorcomputation}/Dockerfile (100%) rename {executor1 => executorcomputation}/mvnw (100%) rename {executor1 => executorcomputation}/mvnw.cmd (100%) rename {executor2 => executorcomputation}/pom.xml (97%) rename executor2/src/main/java/ch/unisg/executor2/Executor2Application.java => executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java (50%) rename {executor2 => executorcomputation}/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java (95%) rename {executor2 => executorcomputation}/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java (85%) rename {executor2 => executorcomputation}/src/main/java/ch/unisg/executor2/executor/domain/Executor.java (93%) rename {executor2 => executorcomputation}/src/main/resources/application.properties (100%) rename executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java => executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java (64%) rename {executor2 => executorrobot}/.gitignore (100%) rename {executor2 => executorrobot}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {executor2 => executorrobot}/.mvn/wrapper/maven-wrapper.jar (100%) rename {executor2 => executorrobot}/.mvn/wrapper/maven-wrapper.properties (100%) rename {executor2 => executorrobot}/Dockerfile (100%) rename {executor2 => executorrobot}/mvnw (100%) rename {executor2 => executorrobot}/mvnw.cmd (100%) rename {executor1 => executorrobot}/pom.xml (98%) rename executor1/src/main/java/ch/unisg/executor1/Executor1Application.java => executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java (53%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java (96%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java (89%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java (90%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java (91%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java (59%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java (58%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java (66%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java (87%) rename {executor1 => executorrobot}/src/main/java/ch/unisg/executor1/executor/domain/Executor.java (73%) rename {executor1 => executorrobot}/src/main/resources/application.properties (100%) rename executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java => executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java (68%) diff --git a/executor1/.gitignore b/executorcomputation/.gitignore similarity index 100% rename from executor1/.gitignore rename to executorcomputation/.gitignore diff --git a/executor1/.mvn/wrapper/MavenWrapperDownloader.java b/executorcomputation/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from executor1/.mvn/wrapper/MavenWrapperDownloader.java rename to executorcomputation/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/executor1/.mvn/wrapper/maven-wrapper.jar b/executorcomputation/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from executor1/.mvn/wrapper/maven-wrapper.jar rename to executorcomputation/.mvn/wrapper/maven-wrapper.jar diff --git a/executor1/.mvn/wrapper/maven-wrapper.properties b/executorcomputation/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from executor1/.mvn/wrapper/maven-wrapper.properties rename to executorcomputation/.mvn/wrapper/maven-wrapper.properties diff --git a/executor1/Dockerfile b/executorcomputation/Dockerfile similarity index 100% rename from executor1/Dockerfile rename to executorcomputation/Dockerfile diff --git a/executor1/mvnw b/executorcomputation/mvnw similarity index 100% rename from executor1/mvnw rename to executorcomputation/mvnw diff --git a/executor1/mvnw.cmd b/executorcomputation/mvnw.cmd similarity index 100% rename from executor1/mvnw.cmd rename to executorcomputation/mvnw.cmd diff --git a/executor2/pom.xml b/executorcomputation/pom.xml similarity index 97% rename from executor2/pom.xml rename to executorcomputation/pom.xml index 1f970e0..f422c55 100644 --- a/executor2/pom.xml +++ b/executorcomputation/pom.xml @@ -9,7 +9,7 @@ ch.unisg - executor2 + executorcomputation 0.0.1-SNAPSHOT executor2 Demo project for Spring Boot diff --git a/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java b/executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java similarity index 50% rename from executor2/src/main/java/ch/unisg/executor2/Executor2Application.java rename to executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java index 03edb3d..81975ba 100644 --- a/executor2/src/main/java/ch/unisg/executor2/Executor2Application.java +++ b/executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java @@ -1,15 +1,15 @@ -package ch.unisg.executor2; +package ch.unisg.executorcomputation; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import ch.unisg.executor2.executor.domain.Executor; +import ch.unisg.executorcomputation.executor.domain.Executor; @SpringBootApplication -public class Executor2Application { +public class ExecutorcomputationApplication { public static void main(String[] args) { - SpringApplication.run(Executor2Application.class, args); + SpringApplication.run(ExecutorcomputationApplication.class, args); Executor.getExecutor(); } diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java b/executorcomputation/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java similarity index 95% rename from executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java rename to executorcomputation/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java index a14b58f..223c88c 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java +++ b/executorcomputation/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java @@ -1,4 +1,4 @@ -package ch.unisg.executor2.executor.adapter.in.web; +package ch.unisg.executorcomputation.executor.adapter.in.web; import java.util.concurrent.CompletableFuture; diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executorcomputation/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java similarity index 85% rename from executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java rename to executorcomputation/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java index 6fa918d..23d9056 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java +++ b/executorcomputation/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java @@ -1,8 +1,8 @@ -package ch.unisg.executor2.executor.application.service; +package ch.unisg.executorcomputation.executor.application.service; import org.springframework.stereotype.Component; -import ch.unisg.executor2.executor.domain.Executor; +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; diff --git a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executorcomputation/src/main/java/ch/unisg/executor2/executor/domain/Executor.java similarity index 93% rename from executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java rename to executorcomputation/src/main/java/ch/unisg/executor2/executor/domain/Executor.java index 4d022b5..c6f2a2b 100644 --- a/executor2/src/main/java/ch/unisg/executor2/executor/domain/Executor.java +++ b/executorcomputation/src/main/java/ch/unisg/executor2/executor/domain/Executor.java @@ -1,4 +1,4 @@ -package ch.unisg.executor2.executor.domain; +package ch.unisg.executorcomputation.executor.domain; import java.util.concurrent.TimeUnit; import ch.unisg.executorBase.executor.domain.ExecutorBase; diff --git a/executor2/src/main/resources/application.properties b/executorcomputation/src/main/resources/application.properties similarity index 100% rename from executor2/src/main/resources/application.properties rename to executorcomputation/src/main/resources/application.properties diff --git a/executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java b/executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java similarity index 64% rename from executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java rename to executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java index 5724a1c..f17d100 100644 --- a/executor2/src/test/java/ch/unisg/executor2/Executor2ApplicationTests.java +++ b/executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java @@ -1,10 +1,10 @@ -package ch.unisg.executor2; +package ch.unisg.executorcomputation; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Executor2ApplicationTests { +class ExecutorcomputationApplicationTests { @Test void contextLoads() { diff --git a/executor2/.gitignore b/executorrobot/.gitignore similarity index 100% rename from executor2/.gitignore rename to executorrobot/.gitignore diff --git a/executor2/.mvn/wrapper/MavenWrapperDownloader.java b/executorrobot/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from executor2/.mvn/wrapper/MavenWrapperDownloader.java rename to executorrobot/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/executor2/.mvn/wrapper/maven-wrapper.jar b/executorrobot/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from executor2/.mvn/wrapper/maven-wrapper.jar rename to executorrobot/.mvn/wrapper/maven-wrapper.jar diff --git a/executor2/.mvn/wrapper/maven-wrapper.properties b/executorrobot/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from executor2/.mvn/wrapper/maven-wrapper.properties rename to executorrobot/.mvn/wrapper/maven-wrapper.properties diff --git a/executor2/Dockerfile b/executorrobot/Dockerfile similarity index 100% rename from executor2/Dockerfile rename to executorrobot/Dockerfile diff --git a/executor2/mvnw b/executorrobot/mvnw similarity index 100% rename from executor2/mvnw rename to executorrobot/mvnw diff --git a/executor2/mvnw.cmd b/executorrobot/mvnw.cmd similarity index 100% rename from executor2/mvnw.cmd rename to executorrobot/mvnw.cmd diff --git a/executor1/pom.xml b/executorrobot/pom.xml similarity index 98% rename from executor1/pom.xml rename to executorrobot/pom.xml index 8a5b9e3..f5348d4 100644 --- a/executor1/pom.xml +++ b/executorrobot/pom.xml @@ -9,7 +9,7 @@ ch.unisg - executor1 + executorrobot 0.0.1-SNAPSHOT executor1 Demo project for Spring Boot diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java similarity index 53% rename from executor1/src/main/java/ch/unisg/executor1/Executor1Application.java rename to executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java index dfb8d8c..fcee5ee 100644 --- a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java @@ -1,15 +1,15 @@ -package ch.unisg.executor1; +package ch.unisg.executorrobot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import ch.unisg.executor1.executor.domain.Executor; +import ch.unisg.executorrobot.executor.domain.Executor; @SpringBootApplication -public class Executor1Application { +public class ExecutorrobotApplication { public static void main(String[] args) { - SpringApplication.run(Executor1Application.class, args); + SpringApplication.run(ExecutorrobotApplication.class, args); Executor.getExecutor(); } diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java similarity index 96% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java index 1f08545..c5348df 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.in.web; +package ch.unisg.executorrobot.executor.adapter.in.web; import java.util.concurrent.CompletableFuture; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java similarity index 89% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java index 94c2309..157bc3e 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out; +package ch.unisg.executorrobot.executor.adapter.out; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -8,7 +8,7 @@ import java.net.http.HttpResponse; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.out.DeleteUserFromRobotPort; +import ch.unisg.executorrobot.executor.application.port.out.DeleteUserFromRobotPort; @Component @Primary diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java similarity index 90% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java index f8b7012..c7507e4 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out; +package ch.unisg.executorrobot.executor.adapter.out; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -8,7 +8,7 @@ import java.net.http.HttpResponse; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.out.InstructionToRobotPort; +import ch.unisg.executorrobot.executor.application.port.out.InstructionToRobotPort; @Component @Primary diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java similarity index 91% rename from executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java index f874892..92ca8c1 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.adapter.out; +package ch.unisg.executorrobot.executor.adapter.out; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -8,7 +8,7 @@ import java.net.http.HttpResponse; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.application.port.out.UserToRobotPort; +import ch.unisg.executorrobot.executor.application.port.out.UserToRobotPort; @Component @Primary diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java similarity index 59% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java index fc6f5d7..2411353 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.application.port.out; +package ch.unisg.executorrobot.executor.application.port.out; public interface DeleteUserFromRobotPort { boolean deleteUserFromRobot(String key); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java similarity index 58% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java index bbf4034..97985b0 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.application.port.out; +package ch.unisg.executorrobot.executor.application.port.out; public interface InstructionToRobotPort { boolean instructionToRobot(String key); diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java similarity index 66% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java index 5b011c1..b5ab55b 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java @@ -1,4 +1,4 @@ -package ch.unisg.executor1.executor.application.port.out; +package ch.unisg.executorrobot.executor.application.port.out; import ch.unisg.executorBase.executor.domain.ExecutorType; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java similarity index 87% rename from executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java index d502053..ce1705a 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -1,8 +1,8 @@ -package ch.unisg.executor1.executor.application.service; +package ch.unisg.executorrobot.executor.application.service; import org.springframework.stereotype.Component; -import ch.unisg.executor1.executor.domain.Executor; +import ch.unisg.executorrobot.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; diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java similarity index 73% rename from executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java rename to executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java index cc11e64..e23dca5 100644 --- a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -1,15 +1,15 @@ -package ch.unisg.executor1.executor.domain; +package ch.unisg.executorrobot.executor.domain; import java.net.http.HttpClient; import java.net.http.HttpResponse; import java.util.concurrent.TimeUnit; -import ch.unisg.executor1.executor.adapter.out.DeleteUserFromRobotAdapter; -import ch.unisg.executor1.executor.adapter.out.InstructionToRobotAdapter; -import ch.unisg.executor1.executor.adapter.out.UserToRobotAdapter; -import ch.unisg.executor1.executor.application.port.out.DeleteUserFromRobotPort; -import ch.unisg.executor1.executor.application.port.out.InstructionToRobotPort; -import ch.unisg.executor1.executor.application.port.out.UserToRobotPort; +import ch.unisg.executorrobot.executor.adapter.out.DeleteUserFromRobotAdapter; +import ch.unisg.executorrobot.executor.adapter.out.InstructionToRobotAdapter; +import ch.unisg.executorrobot.executor.adapter.out.UserToRobotAdapter; +import ch.unisg.executorrobot.executor.application.port.out.DeleteUserFromRobotPort; +import ch.unisg.executorrobot.executor.application.port.out.InstructionToRobotPort; +import ch.unisg.executorrobot.executor.application.port.out.UserToRobotPort; import ch.unisg.executorBase.executor.domain.ExecutorBase; import ch.unisg.executorBase.executor.domain.ExecutorType; diff --git a/executor1/src/main/resources/application.properties b/executorrobot/src/main/resources/application.properties similarity index 100% rename from executor1/src/main/resources/application.properties rename to executorrobot/src/main/resources/application.properties diff --git a/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java b/executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java similarity index 68% rename from executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java rename to executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java index 889c9cd..82f67a8 100644 --- a/executor1/src/test/java/ch/unisg/executor1/Executor1ApplicationTests.java +++ b/executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java @@ -1,10 +1,10 @@ -package ch.unisg.executor1; +package ch.unisg.executorrobot; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Executor1ApplicationTests { +class ExecutorrobotApplicationTests { @Test void contextLoads() { -- 2.45.1 From e0e54f9350309b97a3af8c7cdff243f0cfd8cfb8 Mon Sep 17 00:00:00 2001 From: rahimiankeanu Date: Sun, 7 Nov 2021 23:35:24 +0100 Subject: [PATCH 44/94] Implemented RemovedEventListener... --- .../ExecutorRemovedEventListenerHttpAdapter.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java index 53811f9..fcf9b52 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/http/ExecutorRemovedEventListenerHttpAdapter.java @@ -1,6 +1,14 @@ package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http; +import ch.unisg.tapas.auctionhouse.application.handler.ExecutorRemovedHandler; +import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEvent; +import ch.unisg.tapas.auctionhouse.domain.Auction; +import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** @@ -12,5 +20,13 @@ public class ExecutorRemovedEventListenerHttpAdapter { // TODO: add annotations for request method, request URI, etc. public void handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) { // TODO: implement logic + + ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( + new ExecutorRegistry.ExecutorIdentifier(executorId) + ); + + ExecutorRemovedHandler newExecutorHandler = new ExecutorRemovedHandler(); + newExecutorHandler.handleExecutorRemovedEvent(executorRemovedEvent); + } } -- 2.45.1 From 5c445a2f667d2969d86a6e25f5c4812dbf2f513b Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 8 Nov 2021 14:23:27 +0100 Subject: [PATCH 45/94] rename assignment to roster & added executor registry to roster --- .../port/in/ApplyForTaskUseCase.java | 7 -- {assignment => roster}/.gitignore | 0 .../.mvn/wrapper/MavenWrapperDownloader.java | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 {assignment => roster}/Dockerfile | 0 {assignment => roster}/mvnw | 0 {assignment => roster}/mvnw.cmd | 0 {assignment => roster}/pom.xml | 6 ++ .../ch/unisg/roster/RosterApplication.java | 6 +- .../common/clients/TapasMqttClient.java | 94 ++++++++++++++++++ .../mqtt/AuctionEventMqttListener.java | 11 ++ .../mqtt/AuctionEventsMqttDispatcher.java | 52 ++++++++++ ...ExecutorAddedEventListenerMqttAdapter.java | 44 ++++++++ ...ecutorRemovedEventListenerMqttAdapter.java | 41 ++++++++ .../in/web/ApplyForTaskController.java | 10 +- .../adapter/in/web/DeleteTaskController.java | 8 +- .../adapter/in/web/NewTaskController.java | 8 +- .../in/web/TaskCompletedController.java | 8 +- .../in/web/WebControllerExceptionHandler.java | 2 +- ...llExecutorInExecutorPoolByTypeAdapter.java | 6 +- .../out/web/PublishNewTaskEventAdapter.java | 6 +- .../web/PublishTaskAssignedEventAdapter.java | 6 +- .../web/PublishTaskCompletedEventAdapter.java | 6 +- .../handler/ExecutorAddedHandler.java | 16 +++ .../handler/ExecutorRemovedHandler.java | 19 ++++ .../port/in/ApplyForTaskCommand.java | 4 +- .../port/in/ApplyForTaskUseCase.java | 7 ++ .../port/in/DeleteTaskCommand.java | 4 +- .../port/in/DeleteTaskUseCase.java | 2 +- .../port/in/ExecutorAddedEvent.java | 33 ++++++ .../port/in/ExecutorAddedEventHandler.java | 6 ++ .../port/in/ExecutorRemovedEvent.java | 27 +++++ .../port/in/ExecutorRemovedEventHandler.java | 6 ++ .../application/port/in/NewTaskCommand.java | 4 +- .../application/port/in/NewTaskUseCase.java | 2 +- .../port/in/TaskCompletedCommand.java | 2 +- .../port/in/TaskCompletedUseCase.java | 2 +- ...etAllExecutorInExecutorPoolByTypePort.java | 4 +- .../port/out/NewTaskEventPort.java | 4 +- .../port/out/TaskAssignedEventPort.java | 4 +- .../port/out/TaskCompletedEventPort.java | 4 +- .../service/ApplyForTaskService.java | 14 +-- .../service/DeleteTaskService.java | 8 +- .../application/service/NewTaskService.java | 24 ++--- .../service/TaskCompletedService.java | 12 +-- .../roster/roster}/domain/ExecutorInfo.java | 4 +- .../roster/domain/ExecutorRegistry.java | 92 +++++++++++++++++ .../unisg/roster/roster}/domain/Roster.java | 4 +- .../roster/roster}/domain/RosterItem.java | 2 +- .../ch/unisg/roster/roster}/domain/Task.java | 4 +- .../roster}/domain/event/NewTaskEvent.java | 4 +- .../domain/event/TaskAssignedEvent.java | 2 +- .../domain/event/TaskCompletedEvent.java | 2 +- .../domain/valueobject/ExecutorType.java | 2 +- .../src/main/resources/application.properties | 0 .../unisg/roster/RosterApplicationTests.java | 4 +- 57 files changed, 548 insertions(+), 101 deletions(-) delete mode 100644 assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java rename {assignment => roster}/.gitignore (100%) rename {assignment => roster}/.mvn/wrapper/MavenWrapperDownloader.java (100%) rename {assignment => roster}/.mvn/wrapper/maven-wrapper.jar (100%) rename {assignment => roster}/.mvn/wrapper/maven-wrapper.properties (100%) rename {assignment => roster}/Dockerfile (100%) rename {assignment => roster}/mvnw (100%) rename {assignment => roster}/mvnw.cmd (100%) rename {assignment => roster}/pom.xml (94%) rename assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java => roster/src/main/java/ch/unisg/roster/RosterApplication.java (60%) create mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/in/web/ApplyForTaskController.java (73%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/in/web/DeleteTaskController.java (79%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/in/web/NewTaskController.java (81%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/in/web/TaskCompletedController.java (79%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/in/web/WebControllerExceptionHandler.java (93%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java (89%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/out/web/PublishNewTaskEventAdapter.java (91%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/out/web/PublishTaskAssignedEventAdapter.java (88%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/adapter/out/web/PublishTaskCompletedEventAdapter.java (89%) create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorAddedHandler.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorRemovedHandler.java rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/ApplyForTaskCommand.java (82%) create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskUseCase.java rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/DeleteTaskCommand.java (80%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/DeleteTaskUseCase.java (62%) create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEvent.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEventHandler.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEvent.java create mode 100644 roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEventHandler.java rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/NewTaskCommand.java (80%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/NewTaskUseCase.java (62%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/TaskCompletedCommand.java (91%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/in/TaskCompletedUseCase.java (64%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java (63%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/out/NewTaskEventPort.java (56%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/out/TaskAssignedEventPort.java (60%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/port/out/TaskCompletedEventPort.java (58%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/service/ApplyForTaskService.java (65%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/service/DeleteTaskService.java (67%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/service/NewTaskService.java (53%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/application/service/TaskCompletedService.java (63%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/ExecutorInfo.java (67%) create mode 100644 roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorRegistry.java rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/Roster.java (95%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/RosterItem.java (90%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/Task.java (82%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/event/NewTaskEvent.java (56%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/event/TaskAssignedEvent.java (74%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/event/TaskCompletedEvent.java (85%) rename {assignment/src/main/java/ch/unisg/assignment/assignment => roster/src/main/java/ch/unisg/roster/roster}/domain/valueobject/ExecutorType.java (74%) rename {assignment => roster}/src/main/resources/application.properties (100%) rename assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java => roster/src/test/java/ch/unisg/roster/RosterApplicationTests.java (70%) diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java b/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java deleted file mode 100644 index 1e7180a..0000000 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskUseCase.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.unisg.assignment.assignment.application.port.in; - -import ch.unisg.assignment.assignment.domain.Task; - -public interface ApplyForTaskUseCase { - Task applyForTask(ApplyForTaskCommand applyForTaskCommand); -} diff --git a/assignment/.gitignore b/roster/.gitignore similarity index 100% rename from assignment/.gitignore rename to roster/.gitignore diff --git a/assignment/.mvn/wrapper/MavenWrapperDownloader.java b/roster/.mvn/wrapper/MavenWrapperDownloader.java similarity index 100% rename from assignment/.mvn/wrapper/MavenWrapperDownloader.java rename to roster/.mvn/wrapper/MavenWrapperDownloader.java diff --git a/assignment/.mvn/wrapper/maven-wrapper.jar b/roster/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from assignment/.mvn/wrapper/maven-wrapper.jar rename to roster/.mvn/wrapper/maven-wrapper.jar diff --git a/assignment/.mvn/wrapper/maven-wrapper.properties b/roster/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from assignment/.mvn/wrapper/maven-wrapper.properties rename to roster/.mvn/wrapper/maven-wrapper.properties diff --git a/assignment/Dockerfile b/roster/Dockerfile similarity index 100% rename from assignment/Dockerfile rename to roster/Dockerfile diff --git a/assignment/mvnw b/roster/mvnw similarity index 100% rename from assignment/mvnw rename to roster/mvnw diff --git a/assignment/mvnw.cmd b/roster/mvnw.cmd similarity index 100% rename from assignment/mvnw.cmd rename to roster/mvnw.cmd diff --git a/assignment/pom.xml b/roster/pom.xml similarity index 94% rename from assignment/pom.xml rename to roster/pom.xml index b4650de..f51bff7 100644 --- a/assignment/pom.xml +++ b/roster/pom.xml @@ -56,6 +56,12 @@ 1.2 + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + + org.json json diff --git a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java b/roster/src/main/java/ch/unisg/roster/RosterApplication.java similarity index 60% rename from assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java rename to roster/src/main/java/ch/unisg/roster/RosterApplication.java index 30d7782..dd57a5d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/AssignmentApplication.java +++ b/roster/src/main/java/ch/unisg/roster/RosterApplication.java @@ -1,13 +1,13 @@ -package ch.unisg.assignment; +package ch.unisg.roster; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class AssignmentApplication { +public class RosterApplication { public static void main(String[] args) { - SpringApplication.run(AssignmentApplication.class, args); + SpringApplication.run(RosterApplication.class, args); } } diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java new file mode 100644 index 0000000..8b5411b --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/common/clients/TapasMqttClient.java @@ -0,0 +1,94 @@ +package ch.unisg.roster.roster.adapter.common.clients; + +import ch.unisg.roster.roster.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +/** + * MQTT client for your TAPAS application. This class is defined as a singleton, but it does not have + * to be this way. This class is only provided as an example to help you bootstrap your project. + * You are welcomed to change this class as you see fit. + */ +public class TapasMqttClient { + private static final Logger LOGGER = LogManager.getLogger(TapasMqttClient.class); + + private static TapasMqttClient tapasClient = null; + + private MqttClient mqttClient; + private final String mqttClientId; + private final String brokerAddress; + + private final MessageReceivedCallback messageReceivedCallback; + + private final AuctionEventsMqttDispatcher dispatcher; + + private TapasMqttClient(String brokerAddress, AuctionEventsMqttDispatcher dispatcher) { + this.mqttClientId = UUID.randomUUID().toString(); + this.brokerAddress = brokerAddress; + + this.messageReceivedCallback = new MessageReceivedCallback(); + + this.dispatcher = dispatcher; + } + + public static synchronized TapasMqttClient getInstance(String brokerAddress, + AuctionEventsMqttDispatcher dispatcher) { + + if (tapasClient == null) { + tapasClient = new TapasMqttClient(brokerAddress, dispatcher); + } + + return tapasClient; + } + + public void startReceivingMessages() throws MqttException { + mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence()); + mqttClient.connect(); + mqttClient.setCallback(messageReceivedCallback); + + subscribeToAllTopics(); + } + + public void stopReceivingMessages() throws MqttException { + mqttClient.disconnect(); + } + + private void subscribeToAllTopics() throws MqttException { + for (String topic : dispatcher.getAllTopics()) { + subscribeToTopic(topic); + } + } + + private void subscribeToTopic(String topic) throws MqttException { + mqttClient.subscribe(topic); + } + + private void publishMessage(String topic, String payload) throws MqttException { + MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); + mqttClient.publish(topic, message); + } + + private class MessageReceivedCallback implements MqttCallback { + + @Override + public void connectionLost(Throwable cause) { } + + @Override + public void messageArrived(String topic, MqttMessage message) { + LOGGER.info("Received new MQTT message for topic " + topic + ": " + + new String(message.getPayload())); + + if (topic != null && !topic.isEmpty()) { + dispatcher.dispatchEvent(topic, message); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { } + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java new file mode 100644 index 0000000..6eb109f --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventMqttListener.java @@ -0,0 +1,11 @@ +package ch.unisg.roster.roster.adapter.in.messaging.mqtt; + +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + * Abstract MQTT listener for auction-related events + */ +public abstract class AuctionEventMqttListener { + + public abstract boolean handleEvent(MqttMessage message); +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java new file mode 100644 index 0000000..d19c803 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/AuctionEventsMqttDispatcher.java @@ -0,0 +1,52 @@ +package ch.unisg.roster.roster.adapter.in.messaging.mqtt; + +import org.eclipse.paho.client.mqttv3.*; + +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; + +/** + * Dispatches MQTT messages for known topics to associated event listeners. Used in conjunction with + * {@link ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient}. + * + * This is where you would define MQTT topics and map them to event listeners (see + * {@link AuctionEventsMqttDispatcher#initRouter()}). + * + * This class is only provided as an example to help you bootstrap the project. You are welcomed to + * change this class as you see fit. + */ +public class AuctionEventsMqttDispatcher { + private final Map router; + + public AuctionEventsMqttDispatcher() { + this.router = new Hashtable<>(); + initRouter(); + } + + // TODO: Register here your topics and event listener adapters + private void initRouter() { + router.put("ch/unisg/tapas-group-tutors/executors/added", new ExecutorAddedEventListenerMqttAdapter()); + router.put("ch/unisg/tapas-group-tutors/executors/removed", new ExecutorRemovedEventListenerMqttAdapter()); + } + + /** + * Returns all topics registered with this dispatcher. + * + * @return the set of registered topics + */ + public Set getAllTopics() { + return router.keySet(); + } + + /** + * Dispatches an event received via MQTT for a given topic. + * + * @param topic the topic for which the MQTT message was received + * @param message the received MQTT message + */ + public void dispatchEvent(String topic, MqttMessage message) { + AuctionEventMqttListener listener = router.get(topic); + listener.handleEvent(message); + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java new file mode 100644 index 0000000..dd9257e --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorAddedEventListenerMqttAdapter.java @@ -0,0 +1,44 @@ +package ch.unisg.roster.roster.adapter.in.messaging.mqtt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.roster.roster.application.handler.ExecutorAddedHandler; +import ch.unisg.roster.roster.application.port.in.ExecutorAddedEvent; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; + +public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListener { + private static final Logger LOGGER = LogManager.getLogger(ExecutorAddedEventListenerMqttAdapter.class); + + @Override + public boolean handleEvent(MqttMessage message) { + String payload = new String(message.getPayload()); + + try { + // Note: this messge representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String taskType = data.get("taskType").asText(); + String executorId = data.get("executorURI").asText(); + + ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent( + new ExecutorURI(executorId), + new ExecutorType(taskType) + ); + + ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler(); + newExecutorHandler.handleNewExecutorEvent(executorAddedEvent); + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + + return true; + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java new file mode 100644 index 0000000..d7b5067 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/messaging/mqtt/ExecutorRemovedEventListenerMqttAdapter.java @@ -0,0 +1,41 @@ +package ch.unisg.roster.roster.adapter.in.messaging.mqtt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.roster.roster.application.handler.ExecutorRemovedHandler; +import ch.unisg.roster.roster.application.port.in.ExecutorRemovedEvent; + +public class ExecutorRemovedEventListenerMqttAdapter extends AuctionEventMqttListener { + private static final Logger LOGGER = LogManager.getLogger(ExecutorRemovedEventListenerMqttAdapter.class); + + @Override + public boolean handleEvent(MqttMessage message) { + String payload = new String(message.getPayload()); + + try { + // Note: this messge representation is provided only as an example. You should use a + // representation that makes sense in the context of your application. + JsonNode data = new ObjectMapper().readTree(payload); + + String executorId = data.get("executorURI").asText(); + + ExecutorRemovedEvent executorRemovedEvent = new ExecutorRemovedEvent( + new ExecutorURI(executorId)); + + ExecutorRemovedHandler executorRemovedHandler = new ExecutorRemovedHandler(); + executorRemovedHandler.handleExecutorRemovedEvent(executorRemovedEvent); + + } catch (JsonProcessingException | NullPointerException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + + return true; + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/ApplyForTaskController.java similarity index 73% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/ApplyForTaskController.java index 7b8331c..28170f0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/ApplyForTaskController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/ApplyForTaskController.java @@ -1,13 +1,13 @@ -package ch.unisg.assignment.assignment.adapter.in.web; +package ch.unisg.roster.roster.adapter.in.web; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; -import ch.unisg.assignment.assignment.domain.ExecutorInfo; -import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.roster.roster.application.port.in.ApplyForTaskCommand; +import ch.unisg.roster.roster.application.port.in.ApplyForTaskUseCase; +import ch.unisg.roster.roster.domain.ExecutorInfo; +import ch.unisg.roster.roster.domain.Task; @RestController public class ApplyForTaskController { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/DeleteTaskController.java similarity index 79% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/DeleteTaskController.java index b34e6db..eef8b71 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/DeleteTaskController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/DeleteTaskController.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.in.web; +package ch.unisg.roster.roster.adapter.in.web; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -6,9 +6,9 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import ch.unisg.assignment.assignment.application.port.in.DeleteTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.DeleteTaskUseCase; -import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.roster.roster.application.port.in.DeleteTaskCommand; +import ch.unisg.roster.roster.application.port.in.DeleteTaskUseCase; +import ch.unisg.roster.roster.domain.Task; @RestController public class DeleteTaskController { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java similarity index 81% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java index 9faf2ec..af01346 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/NewTaskController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/NewTaskController.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.in.web; +package ch.unisg.roster.roster.adapter.in.web; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -6,9 +6,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; -import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.roster.roster.application.port.in.NewTaskCommand; +import ch.unisg.roster.roster.application.port.in.NewTaskUseCase; +import ch.unisg.roster.roster.domain.Task; @RestController public class NewTaskController { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java similarity index 79% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java index df89c7f..f81db32 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/TaskCompletedController.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/TaskCompletedController.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.in.web; +package ch.unisg.roster.roster.adapter.in.web; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -6,9 +6,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand; -import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase; -import ch.unisg.assignment.assignment.domain.Task; +import ch.unisg.roster.roster.application.port.in.TaskCompletedCommand; +import ch.unisg.roster.roster.application.port.in.TaskCompletedUseCase; +import ch.unisg.roster.roster.domain.Task; @RestController public class TaskCompletedController { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/WebControllerExceptionHandler.java similarity index 93% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/WebControllerExceptionHandler.java index 19cce0d..f0a4974 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/in/web/WebControllerExceptionHandler.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/in/web/WebControllerExceptionHandler.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.in.web; +package ch.unisg.roster.roster.adapter.in.web; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java similarity index 89% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java index 1c02839..df444ca 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/GetAllExecutorInExecutorPoolByTypeAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.out.web; +package ch.unisg.roster.roster.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -14,8 +14,8 @@ import org.springframework.context.annotation.Primary; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.out.GetAllExecutorInExecutorPoolByTypePort; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.application.port.out.GetAllExecutorInExecutorPoolByTypePort; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; @Component @Primary diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java similarity index 91% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java index 10638d3..6a6b7f7 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishNewTaskEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishNewTaskEventAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.out.web; +package ch.unisg.roster.roster.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -12,8 +12,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; -import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; +import ch.unisg.roster.roster.application.port.out.NewTaskEventPort; +import ch.unisg.roster.roster.domain.event.NewTaskEvent; @Component @Primary diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java similarity index 88% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java index 45a10f3..c71e306 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskAssignedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskAssignedEventAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.out.web; +package ch.unisg.roster.roster.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -13,8 +13,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; -import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; +import ch.unisg.roster.roster.application.port.out.TaskAssignedEventPort; +import ch.unisg.roster.roster.domain.event.TaskAssignedEvent; @Component @Primary diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java similarity index 89% rename from assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java rename to roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java index e9c4944..7038291 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/adapter/out/web/PublishTaskCompletedEventAdapter.java +++ b/roster/src/main/java/ch/unisg/roster/roster/adapter/out/web/PublishTaskCompletedEventAdapter.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.adapter.out.web; +package ch.unisg.roster.roster.adapter.out.web; import java.io.IOException; import java.net.URI; @@ -13,8 +13,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; -import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; +import ch.unisg.roster.roster.application.port.out.TaskCompletedEventPort; +import ch.unisg.roster.roster.domain.event.TaskCompletedEvent; @Component @Primary diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorAddedHandler.java b/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorAddedHandler.java new file mode 100644 index 0000000..9545c07 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorAddedHandler.java @@ -0,0 +1,16 @@ +package ch.unisg.roster.roster.application.handler; + +import ch.unisg.roster.roster.application.port.in.ExecutorAddedEvent; +import ch.unisg.roster.roster.application.port.in.ExecutorAddedEventHandler; +import ch.unisg.roster.roster.domain.ExecutorRegistry; +import org.springframework.stereotype.Component; + +@Component +public class ExecutorAddedHandler implements ExecutorAddedEventHandler { + + @Override + public boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent) { + return ExecutorRegistry.getInstance().addExecutor(executorAddedEvent.getExecutorType(), + executorAddedEvent.getExecutorURI()); + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorRemovedHandler.java b/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorRemovedHandler.java new file mode 100644 index 0000000..c6e3f68 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/handler/ExecutorRemovedHandler.java @@ -0,0 +1,19 @@ +package ch.unisg.roster.roster.application.handler; + +import ch.unisg.roster.roster.application.port.in.ExecutorRemovedEvent; +import ch.unisg.roster.roster.application.port.in.ExecutorRemovedEventHandler; +import ch.unisg.roster.roster.domain.ExecutorRegistry; +import org.springframework.stereotype.Component; + +/** + * Handler for executor removed events. It removes the executor from this roster's executor + * registry. + */ +@Component +public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler { + + @Override + public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) { + return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorURI()); + } +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskCommand.java similarity index 82% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskCommand.java index bdc16d9..f03ef5f 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/ApplyForTaskCommand.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskCommand.java @@ -1,8 +1,8 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; import javax.validation.constraints.NotNull; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import ch.unisg.common.validation.SelfValidating; import ch.unisg.common.valueobject.ExecutorURI; import lombok.EqualsAndHashCode; diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskUseCase.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskUseCase.java new file mode 100644 index 0000000..61b7bd4 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ApplyForTaskUseCase.java @@ -0,0 +1,7 @@ +package ch.unisg.roster.roster.application.port.in; + +import ch.unisg.roster.roster.domain.Task; + +public interface ApplyForTaskUseCase { + Task applyForTask(ApplyForTaskCommand applyForTaskCommand); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskCommand.java similarity index 80% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskCommand.java index 7239acc..9f59dc3 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskCommand.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskCommand.java @@ -1,8 +1,8 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; import javax.validation.constraints.NotNull; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import ch.unisg.common.validation.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskUseCase.java similarity index 62% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskUseCase.java index e890e8b..2acfc63 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/DeleteTaskUseCase.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/DeleteTaskUseCase.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; public interface DeleteTaskUseCase { boolean deleteTask(DeleteTaskCommand deleteTaskCommand); diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEvent.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEvent.java new file mode 100644 index 0000000..0e10b8e --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEvent.java @@ -0,0 +1,33 @@ +package ch.unisg.roster.roster.application.port.in; + +import lombok.Value; + +import javax.validation.constraints.NotNull; + +import ch.unisg.common.validation.SelfValidating; +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; + +/** + * Event that notifies the auction house that an executor has been added to this TAPAS application. + */ +@Value +public class ExecutorAddedEvent extends SelfValidating { + @NotNull + private final ExecutorURI executorURI; + + @NotNull + private final ExecutorType executorType; + + /** + * Constructs an executor added event. + * + * @param executorURI the identifier of the executor that was added to this TAPAS application + */ + public ExecutorAddedEvent(ExecutorURI executorURI, ExecutorType executorType) { + this.executorURI = executorURI; + this.executorType = executorType; + + this.validateSelf(); + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEventHandler.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEventHandler.java new file mode 100644 index 0000000..c7a9076 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorAddedEventHandler.java @@ -0,0 +1,6 @@ +package ch.unisg.roster.roster.application.port.in; + +public interface ExecutorAddedEventHandler { + + boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent); +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEvent.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEvent.java new file mode 100644 index 0000000..8753683 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEvent.java @@ -0,0 +1,27 @@ +package ch.unisg.roster.roster.application.port.in; + +import lombok.Value; + +import javax.validation.constraints.NotNull; + +import ch.unisg.common.validation.SelfValidating; +import ch.unisg.common.valueobject.ExecutorURI; + +/** + * Event that notifies the auction house that an executor has been removed from this TAPAS application. + */ +@Value +public class ExecutorRemovedEvent extends SelfValidating { + @NotNull + private final ExecutorURI executorURI; + + /** + * Constructs an executor removed event. + * + * @param executorURI the identifier of the executor that was removed from this TAPAS application + */ + public ExecutorRemovedEvent(ExecutorURI executorURI) { + this.executorURI = executorURI; + this.validateSelf(); + } +} diff --git a/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEventHandler.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEventHandler.java new file mode 100644 index 0000000..79ee6a7 --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/ExecutorRemovedEventHandler.java @@ -0,0 +1,6 @@ +package ch.unisg.roster.roster.application.port.in; + +public interface ExecutorRemovedEventHandler { + + boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent); +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java similarity index 80% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java index f06798b..92a7403 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskCommand.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskCommand.java @@ -1,8 +1,8 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; import javax.validation.constraints.NotNull; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import ch.unisg.common.validation.SelfValidating; import lombok.EqualsAndHashCode; import lombok.Value; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskUseCase.java similarity index 62% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskUseCase.java index 21f084e..f1bd733 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/NewTaskUseCase.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/NewTaskUseCase.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; public interface NewTaskUseCase { boolean addNewTaskToQueue(NewTaskCommand newTaskCommand); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedCommand.java similarity index 91% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedCommand.java index 08dc8eb..b7438c0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedCommand.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedCommand.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; import javax.validation.constraints.NotNull; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedUseCase.java similarity index 64% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedUseCase.java index 1902952..51b305a 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/in/TaskCompletedUseCase.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/in/TaskCompletedUseCase.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.application.port.in; +package ch.unisg.roster.roster.application.port.in; public interface TaskCompletedUseCase { void taskCompleted(TaskCompletedCommand taskCompletedCommand); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java similarity index 63% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java index 9f6c824..f32a3f5 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/GetAllExecutorInExecutorPoolByTypePort.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.application.port.out; +package ch.unisg.roster.roster.application.port.out; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; public interface GetAllExecutorInExecutorPoolByTypePort { /** diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/NewTaskEventPort.java similarity index 56% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/out/NewTaskEventPort.java index 243c7f2..75fda6d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/NewTaskEventPort.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/NewTaskEventPort.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.application.port.out; +package ch.unisg.roster.roster.application.port.out; -import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; +import ch.unisg.roster.roster.domain.event.NewTaskEvent; public interface NewTaskEventPort { /** diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskAssignedEventPort.java similarity index 60% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskAssignedEventPort.java index 5f55ec8..2bcb2ae 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskAssignedEventPort.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskAssignedEventPort.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.application.port.out; +package ch.unisg.roster.roster.application.port.out; -import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; +import ch.unisg.roster.roster.domain.event.TaskAssignedEvent; public interface TaskAssignedEventPort { /** diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskCompletedEventPort.java similarity index 58% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java rename to roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskCompletedEventPort.java index 83ad179..a8c11ef 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/port/out/TaskCompletedEventPort.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/port/out/TaskCompletedEventPort.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.application.port.out; +package ch.unisg.roster.roster.application.port.out; -import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; +import ch.unisg.roster.roster.domain.event.TaskCompletedEvent; public interface TaskCompletedEventPort { /** diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/ApplyForTaskService.java similarity index 65% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java rename to roster/src/main/java/ch/unisg/roster/roster/application/service/ApplyForTaskService.java index dfb70e0..26b75aa 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/ApplyForTaskService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/ApplyForTaskService.java @@ -1,15 +1,15 @@ -package ch.unisg.assignment.assignment.application.service; +package ch.unisg.roster.roster.application.service; import javax.transaction.Transactional; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase; -import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort; -import ch.unisg.assignment.assignment.domain.Roster; -import ch.unisg.assignment.assignment.domain.Task; -import ch.unisg.assignment.assignment.domain.event.TaskAssignedEvent; +import ch.unisg.roster.roster.application.port.in.ApplyForTaskCommand; +import ch.unisg.roster.roster.application.port.in.ApplyForTaskUseCase; +import ch.unisg.roster.roster.application.port.out.TaskAssignedEventPort; +import ch.unisg.roster.roster.domain.Roster; +import ch.unisg.roster.roster.domain.Task; +import ch.unisg.roster.roster.domain.event.TaskAssignedEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/DeleteTaskService.java similarity index 67% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java rename to roster/src/main/java/ch/unisg/roster/roster/application/service/DeleteTaskService.java index 7d67e4a..a6b4841 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/DeleteTaskService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/DeleteTaskService.java @@ -1,12 +1,12 @@ -package ch.unisg.assignment.assignment.application.service; +package ch.unisg.roster.roster.application.service; import javax.transaction.Transactional; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.in.DeleteTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.DeleteTaskUseCase; -import ch.unisg.assignment.assignment.domain.Roster; +import ch.unisg.roster.roster.application.port.in.DeleteTaskCommand; +import ch.unisg.roster.roster.application.port.in.DeleteTaskUseCase; +import ch.unisg.roster.roster.domain.Roster; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java similarity index 53% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java rename to roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java index d240a4b..588ed04 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/NewTaskService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/NewTaskService.java @@ -1,16 +1,16 @@ -package ch.unisg.assignment.assignment.application.service; +package ch.unisg.roster.roster.application.service; import javax.transaction.Transactional; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand; -import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase; -import ch.unisg.assignment.assignment.application.port.out.GetAllExecutorInExecutorPoolByTypePort; -import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort; -import ch.unisg.assignment.assignment.domain.Roster; -import ch.unisg.assignment.assignment.domain.Task; -import ch.unisg.assignment.assignment.domain.event.NewTaskEvent; +import ch.unisg.roster.roster.application.port.in.NewTaskCommand; +import ch.unisg.roster.roster.application.port.in.NewTaskUseCase; +import ch.unisg.roster.roster.application.port.out.NewTaskEventPort; +import ch.unisg.roster.roster.domain.ExecutorRegistry; +import ch.unisg.roster.roster.domain.Roster; +import ch.unisg.roster.roster.domain.Task; +import ch.unisg.roster.roster.domain.event.NewTaskEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor; public class NewTaskService implements NewTaskUseCase { private final NewTaskEventPort newTaskEventPort; - private final GetAllExecutorInExecutorPoolByTypePort getAllExecutorInExecutorPoolByTypePort; /** * Checks if we can execute the give task, if yes the task gets added to the task queue and return true. @@ -29,9 +28,10 @@ public class NewTaskService implements NewTaskUseCase { @Override public boolean addNewTaskToQueue(NewTaskCommand command) { - // if (!getAllExecutorInExecutorPoolByTypePort.doesExecutorTypeExist(command.getTaskType())) { - // return false; - // } + ExecutorRegistry executorRegistry = ExecutorRegistry.getInstance(); + if (!executorRegistry.containsTaskType(command.getTaskType())) { + return false; + } Task task = new Task(command.getTaskID(), command.getTaskType()); diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java b/roster/src/main/java/ch/unisg/roster/roster/application/service/TaskCompletedService.java similarity index 63% rename from assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java rename to roster/src/main/java/ch/unisg/roster/roster/application/service/TaskCompletedService.java index 7c3e7f6..69b65d1 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/application/service/TaskCompletedService.java +++ b/roster/src/main/java/ch/unisg/roster/roster/application/service/TaskCompletedService.java @@ -1,14 +1,14 @@ -package ch.unisg.assignment.assignment.application.service; +package ch.unisg.roster.roster.application.service; import javax.transaction.Transactional; import org.springframework.stereotype.Component; -import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand; -import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase; -import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort; -import ch.unisg.assignment.assignment.domain.Roster; -import ch.unisg.assignment.assignment.domain.event.TaskCompletedEvent; +import ch.unisg.roster.roster.application.port.in.TaskCompletedCommand; +import ch.unisg.roster.roster.application.port.in.TaskCompletedUseCase; +import ch.unisg.roster.roster.application.port.out.TaskCompletedEventPort; +import ch.unisg.roster.roster.domain.Roster; +import ch.unisg.roster.roster.domain.event.TaskCompletedEvent; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java b/roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorInfo.java similarity index 67% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorInfo.java index 58b47dc..eb32ec0 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/ExecutorInfo.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorInfo.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.roster.roster.domain; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import ch.unisg.common.valueobject.ExecutorURI; import lombok.Getter; import lombok.Setter; diff --git a/roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorRegistry.java b/roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorRegistry.java new file mode 100644 index 0000000..4ddba0f --- /dev/null +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/ExecutorRegistry.java @@ -0,0 +1,92 @@ +package ch.unisg.roster.roster.domain; + +import java.util.*; + +import ch.unisg.common.valueobject.ExecutorURI; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; + +/** + * Registry that keeps a track of executors internal to the TAPAS application and the types of tasks + * they can achieve. One executor may correspond to multiple task types. + * This class is a singleton. + */ +public class ExecutorRegistry { + private static ExecutorRegistry registry; + + private final Map> executors; + + private ExecutorRegistry() { + this.executors = new Hashtable<>(); + } + + public static synchronized ExecutorRegistry getInstance() { + if (registry == null) { + registry = new ExecutorRegistry(); + } + + return registry; + } + + /** + * Adds an executor to the registry for a given task type. + * + * @param taskType the type of the task + * @param executorIdentifier the identifier of the executor (can be any string) + * @return true unless a runtime exception occurs + */ + public boolean addExecutor(ExecutorType executorType, ExecutorURI executorURI) { + Set taskTypeExecs = executors.getOrDefault(executorType, + Collections.synchronizedSet(new HashSet<>())); + + taskTypeExecs.add(executorURI); + executors.put(executorType, taskTypeExecs); + + return true; + } + + /** + * Removes an executor from the registry. The executor is disassociated from all known task types. + * + * @param executorURI the identifier of the executor + * @return true unless a runtime exception occurs + */ + public boolean removeExecutor(ExecutorURI executorURI) { + Iterator iterator = executors.keySet().iterator(); + + while (iterator.hasNext()) { + ExecutorType executorType = iterator.next(); + Set set = executors.get(executorType); + + set.remove(executorURI); + + if (set.isEmpty()) { + iterator.remove(); + } + } + + return true; + } + + /** + * Checks if the registry contains an executor for a given task type. Used during task creation + * to decide if a task can be executed. + * + * @param taskType the task type being auctioned + * @return + */ + public boolean containsTaskType(ExecutorType taskType) { + return executors.containsKey(taskType); + } + + /** + * Adds a list of executors to current executor list. Should only be used on startup to + * add all available executors from the executor pool to the registry. + * + * @param executors the initial executors + * @return + */ + public void init(Map> executors) { + this.executors.putAll(executors); + } + +} diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java b/roster/src/main/java/ch/unisg/roster/roster/domain/Roster.java similarity index 95% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/Roster.java index fb259c1..cc9a0a6 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Roster.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/Roster.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.roster.roster.domain; import java.util.ArrayList; import java.util.Arrays; @@ -6,7 +6,7 @@ import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import ch.unisg.common.valueobject.ExecutorURI; public class Roster { diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java b/roster/src/main/java/ch/unisg/roster/roster/domain/RosterItem.java similarity index 90% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/RosterItem.java index b405f44..cc39c6c 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/RosterItem.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/RosterItem.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.roster.roster.domain; import ch.unisg.common.valueobject.ExecutorURI; import lombok.Getter; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java b/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java similarity index 82% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/Task.java index 7daa738..40ef9fa 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/Task.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/Task.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.domain; +package ch.unisg.roster.roster.domain; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; import lombok.Getter; import lombok.Setter; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java b/roster/src/main/java/ch/unisg/roster/roster/domain/event/NewTaskEvent.java similarity index 56% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/event/NewTaskEvent.java index 34e7f0b..1457f1d 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/NewTaskEvent.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/event/NewTaskEvent.java @@ -1,6 +1,6 @@ -package ch.unisg.assignment.assignment.domain.event; +package ch.unisg.roster.roster.domain.event; -import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType; +import ch.unisg.roster.roster.domain.valueobject.ExecutorType; public class NewTaskEvent { public final ExecutorType taskType; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java b/roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskAssignedEvent.java similarity index 74% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskAssignedEvent.java index d0178d4..9c57270 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskAssignedEvent.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskAssignedEvent.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain.event; +package ch.unisg.roster.roster.domain.event; public class TaskAssignedEvent { public final String taskID; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java b/roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskCompletedEvent.java similarity index 85% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskCompletedEvent.java index 432a8f0..926f601 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/event/TaskCompletedEvent.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/event/TaskCompletedEvent.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain.event; +package ch.unisg.roster.roster.domain.event; public class TaskCompletedEvent { public final String taskID; diff --git a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java b/roster/src/main/java/ch/unisg/roster/roster/domain/valueobject/ExecutorType.java similarity index 74% rename from assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java rename to roster/src/main/java/ch/unisg/roster/roster/domain/valueobject/ExecutorType.java index bc5f467..72368e3 100644 --- a/assignment/src/main/java/ch/unisg/assignment/assignment/domain/valueobject/ExecutorType.java +++ b/roster/src/main/java/ch/unisg/roster/roster/domain/valueobject/ExecutorType.java @@ -1,4 +1,4 @@ -package ch.unisg.assignment.assignment.domain.valueobject; +package ch.unisg.roster.roster.domain.valueobject; import lombok.Value; diff --git a/assignment/src/main/resources/application.properties b/roster/src/main/resources/application.properties similarity index 100% rename from assignment/src/main/resources/application.properties rename to roster/src/main/resources/application.properties diff --git a/assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java b/roster/src/test/java/ch/unisg/roster/RosterApplicationTests.java similarity index 70% rename from assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java rename to roster/src/test/java/ch/unisg/roster/RosterApplicationTests.java index 9da24b5..5ee712b 100644 --- a/assignment/src/test/java/ch/unisg/assignment/AssignmentApplicationTests.java +++ b/roster/src/test/java/ch/unisg/roster/RosterApplicationTests.java @@ -1,10 +1,10 @@ -package ch.unisg.assignment; +package ch.unisg.roster; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class AssignmentApplicationTests { +class RosterApplicationTests { @Test void contextLoads() { -- 2.45.1 From ee06c0b80e0b325eedfe951426a0a55e6fc32fca Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 8 Nov 2021 14:48:12 +0100 Subject: [PATCH 46/94] error fixes --- .../ExecutorcomputationApplication.java | 0 .../executor/adapter/in/web/TaskAvailableController.java | 0 .../executor/application/service/TaskAvailableService.java | 0 .../executor/domain/Executor.java | 0 .../ExecutorcomputationApplicationTests.java | 0 .../ExecutorrobotApplication.java | 0 .../executor/adapter/in/web/TaskAvailableController.java | 0 .../executor/adapter/out/DeleteUserFromRobotAdapter.java | 0 .../executor/adapter/out/InstructionToRobotAdapter.java | 0 .../executor/adapter/out/UserToRobotAdapter.java | 0 .../application/port/out/DeleteUserFromRobotPort.java | 0 .../executor/application/port/out/InstructionToRobotPort.java | 0 .../executor/application/port/out/UserToRobotPort.java | 0 .../executor/application/service/TaskAvailableService.java | 0 .../executor/domain/Executor.java | 4 +--- .../ExecutorrobotApplicationTests.java | 0 16 files changed, 1 insertion(+), 3 deletions(-) rename executorcomputation/src/main/java/ch/unisg/{executor2 => executorcomputation}/ExecutorcomputationApplication.java (100%) rename executorcomputation/src/main/java/ch/unisg/{executor2 => executorcomputation}/executor/adapter/in/web/TaskAvailableController.java (100%) rename executorcomputation/src/main/java/ch/unisg/{executor2 => executorcomputation}/executor/application/service/TaskAvailableService.java (100%) rename executorcomputation/src/main/java/ch/unisg/{executor2 => executorcomputation}/executor/domain/Executor.java (100%) rename executorcomputation/src/test/java/ch/unisg/{executor2 => executorcomputation}/ExecutorcomputationApplicationTests.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/ExecutorrobotApplication.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/adapter/in/web/TaskAvailableController.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/adapter/out/DeleteUserFromRobotAdapter.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/adapter/out/InstructionToRobotAdapter.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/adapter/out/UserToRobotAdapter.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/application/port/out/DeleteUserFromRobotPort.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/application/port/out/InstructionToRobotPort.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/application/port/out/UserToRobotPort.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/application/service/TaskAvailableService.java (100%) rename executorrobot/src/main/java/ch/unisg/{executor1 => executorrobot}/executor/domain/Executor.java (95%) rename executorrobot/src/test/java/ch/unisg/{executor1 => executorrobot}/ExecutorrobotApplicationTests.java (100%) diff --git a/executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java b/executorcomputation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executor2/ExecutorcomputationApplication.java rename to executorcomputation/src/main/java/ch/unisg/executorcomputation/ExecutorcomputationApplication.java diff --git a/executorcomputation/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java b/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executor2/executor/adapter/in/web/TaskAvailableController.java rename to executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/adapter/in/web/TaskAvailableController.java diff --git a/executorcomputation/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java b/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executor2/executor/application/service/TaskAvailableService.java rename to executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/application/service/TaskAvailableService.java diff --git a/executorcomputation/src/main/java/ch/unisg/executor2/executor/domain/Executor.java b/executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java similarity index 100% rename from executorcomputation/src/main/java/ch/unisg/executor2/executor/domain/Executor.java rename to executorcomputation/src/main/java/ch/unisg/executorcomputation/executor/domain/Executor.java diff --git a/executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java b/executorcomputation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java similarity index 100% rename from executorcomputation/src/test/java/ch/unisg/executor2/ExecutorcomputationApplicationTests.java rename to executorcomputation/src/test/java/ch/unisg/executorcomputation/ExecutorcomputationApplicationTests.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java b/executorrobot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/ExecutorrobotApplication.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/ExecutorrobotApplication.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/in/web/TaskAvailableController.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/DeleteUserFromRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/DeleteUserFromRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/InstructionToRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/InstructionToRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/adapter/out/UserToRobotAdapter.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/adapter/out/UserToRobotAdapter.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/DeleteUserFromRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/DeleteUserFromRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/InstructionToRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/InstructionToRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/application/port/out/UserToRobotPort.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/port/out/UserToRobotPort.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java similarity index 100% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/application/service/TaskAvailableService.java diff --git a/executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java similarity index 95% rename from executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java rename to executorrobot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java index 44ddcb4..4124e9e 100644 --- a/executorrobot/src/main/java/ch/unisg/executor1/executor/domain/Executor.java +++ b/executorrobot/src/main/java/ch/unisg/executorrobot/executor/domain/Executor.java @@ -1,7 +1,5 @@ package ch.unisg.executorrobot.executor.domain; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; import java.util.concurrent.TimeUnit; import ch.unisg.executorrobot.executor.adapter.out.DeleteUserFromRobotAdapter; @@ -30,7 +28,7 @@ public class Executor extends ExecutorBase { @Override protected - String execution() { + String execution(String... input) { String key = userToRobotPort.userToRobot(); try { diff --git a/executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java b/executorrobot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java similarity index 100% rename from executorrobot/src/test/java/ch/unisg/executor1/ExecutorrobotApplicationTests.java rename to executorrobot/src/test/java/ch/unisg/executorrobot/ExecutorrobotApplicationTests.java -- 2.45.1 From f2fb9450640cafe01af6c1bd6f34cb7f57a4f6b7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 8 Nov 2021 15:16:26 +0100 Subject: [PATCH 47/94] naming error fixes --- executor-base/pom.xml | 4 ++-- executorcomputation/pom.xml | 4 ++-- executorrobot/pom.xml | 4 ++-- roster/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/executor-base/pom.xml b/executor-base/pom.xml index 80c9739..7893d45 100644 --- a/executor-base/pom.xml +++ b/executor-base/pom.xml @@ -9,9 +9,9 @@ ch.unisg - executorBase + executorbase 0.0.1-SNAPSHOT - executorBase + executorbase Demo project for Spring Boot 11 diff --git a/executorcomputation/pom.xml b/executorcomputation/pom.xml index f422c55..b9e45ac 100644 --- a/executorcomputation/pom.xml +++ b/executorcomputation/pom.xml @@ -11,7 +11,7 @@ ch.unisg executorcomputation 0.0.1-SNAPSHOT - executor2 + executorcomputation Demo project for Spring Boot 11 @@ -42,7 +42,7 @@ ch.unisg - executorBase + executorbase 0.0.1-SNAPSHOT compile diff --git a/executorrobot/pom.xml b/executorrobot/pom.xml index f5348d4..101c268 100644 --- a/executorrobot/pom.xml +++ b/executorrobot/pom.xml @@ -11,7 +11,7 @@ ch.unisg executorrobot 0.0.1-SNAPSHOT - executor1 + executorrobot Demo project for Spring Boot 11 @@ -42,7 +42,7 @@ ch.unisg - executorBase + executorbase 0.0.1-SNAPSHOT diff --git a/roster/pom.xml b/roster/pom.xml index f51bff7..791e0d0 100644 --- a/roster/pom.xml +++ b/roster/pom.xml @@ -9,9 +9,9 @@ ch.unisg - assignment + roster 0.0.1-SNAPSHOT - assignment + roster Demo project for Spring Boot 11 -- 2.45.1 From 8cfdd5ff094fd8e31c569a46e862928e8aea7d8a Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 9 Nov 2021 21:25:02 +0100 Subject: [PATCH 48/94] Fixed some spelling mistakes and fixed an error in the Task list that produced a build error --- ...torsInExecutorPoolByTypeWebController.java} | 18 +++++++++--------- ...AllExecutorInExecutorPoolByTypeUseCase.java | 9 --------- ...AllExecutorsInExecutorPoolByTypeQuery.java} | 4 ++-- ...llExecutorsInExecutorPoolByTypeUseCase.java | 9 +++++++++ ...lExecutorsInExecutorPoolByTypeService.java} | 8 ++++---- .../application/service/DeleteTaskService.java | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) rename executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/{GetAllExecutorInExecutorPoolByTypeWebController.java => GetAllExecutorsInExecutorPoolByTypeWebController.java} (50%) delete mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java rename executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/{GetAllExecutorInExecutorPoolByTypeQuery.java => GetAllExecutorsInExecutorPoolByTypeQuery.java} (64%) create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeUseCase.java rename executor-pool/src/main/java/ch/unisg/executorpool/application/service/{GetAllExecutorInExecutorPoolByTypeService.java => GetAllExecutorsInExecutorPoolByTypeService.java} (56%) diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java similarity index 50% rename from executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java rename to executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java index 2595781..dbea300 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorInExecutorPoolByTypeWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java @@ -1,7 +1,7 @@ package ch.unisg.executorpool.adapter.in.web; -import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeQuery; -import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeUseCase; +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeQuery; +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeUseCase; import ch.unisg.executorpool.domain.ExecutorClass; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; import org.springframework.http.HttpHeaders; @@ -14,17 +14,17 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController -public class GetAllExecutorInExecutorPoolByTypeWebController { - private final GetAllExecutorInExecutorPoolByTypeUseCase getAllExecutorInExecutorPoolByTypeUseCase; +public class GetAllExecutorsInExecutorPoolByTypeWebController { + private final GetAllExecutorsInExecutorPoolByTypeUseCase getAllExecutorsInExecutorPoolByTypeUseCase; - public GetAllExecutorInExecutorPoolByTypeWebController(GetAllExecutorInExecutorPoolByTypeUseCase getAllExecutorInExecutorPoolByTypeUseCase){ - this.getAllExecutorInExecutorPoolByTypeUseCase = getAllExecutorInExecutorPoolByTypeUseCase; + public GetAllExecutorsInExecutorPoolByTypeWebController(GetAllExecutorsInExecutorPoolByTypeUseCase getAllExecutorInExecutorPoolByTypeUseCase){ + this.getAllExecutorsInExecutorPoolByTypeUseCase = getAllExecutorInExecutorPoolByTypeUseCase; } - @GetMapping(path = "/executor-pool/GetAllExecutorInExecutorPoolByType/{taskType}") + @GetMapping(path = "/executor-pool/GetAllExecutorsInExecutorPoolByType/{taskType}") public ResponseEntity getAllExecutorInExecutorPoolByType(@PathVariable("taskType") String taskType){ - GetAllExecutorInExecutorPoolByTypeQuery query = new GetAllExecutorInExecutorPoolByTypeQuery(new ExecutorTaskType(taskType)); - List matchedExecutors = getAllExecutorInExecutorPoolByTypeUseCase.getAllExecutorInExecutorPoolByType(query); + GetAllExecutorsInExecutorPoolByTypeQuery query = new GetAllExecutorsInExecutorPoolByTypeQuery(new ExecutorTaskType(taskType)); + List matchedExecutors = getAllExecutorsInExecutorPoolByTypeUseCase.getAllExecutorsInExecutorPoolByType(query); // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java deleted file mode 100644 index 9f612bf..0000000 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeUseCase.java +++ /dev/null @@ -1,9 +0,0 @@ -package ch.unisg.executorpool.application.port.in; - -import ch.unisg.executorpool.domain.ExecutorClass; - -import java.util.List; - -public interface GetAllExecutorInExecutorPoolByTypeUseCase { - List getAllExecutorInExecutorPoolByType(GetAllExecutorInExecutorPoolByTypeQuery query); -} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeQuery.java similarity index 64% rename from executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java rename to executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeQuery.java index c812eab..079e7e1 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorInExecutorPoolByTypeQuery.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeQuery.java @@ -7,11 +7,11 @@ import lombok.Value; import javax.validation.constraints.NotNull; @Value -public class GetAllExecutorInExecutorPoolByTypeQuery extends SelfValidating { +public class GetAllExecutorsInExecutorPoolByTypeQuery extends SelfValidating { @NotNull private final ExecutorTaskType executorTaskType; - public GetAllExecutorInExecutorPoolByTypeQuery(ExecutorTaskType executorTaskType){ + public GetAllExecutorsInExecutorPoolByTypeQuery(ExecutorTaskType executorTaskType){ this.executorTaskType = executorTaskType; this.validateSelf(); } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeUseCase.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeUseCase.java new file mode 100644 index 0000000..4821284 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/GetAllExecutorsInExecutorPoolByTypeUseCase.java @@ -0,0 +1,9 @@ +package ch.unisg.executorpool.application.port.in; + +import ch.unisg.executorpool.domain.ExecutorClass; + +import java.util.List; + +public interface GetAllExecutorsInExecutorPoolByTypeUseCase { + List getAllExecutorsInExecutorPoolByType(GetAllExecutorsInExecutorPoolByTypeQuery query); +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolByTypeService.java similarity index 56% rename from executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java rename to executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolByTypeService.java index 74988b2..00d1636 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorInExecutorPoolByTypeService.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/GetAllExecutorsInExecutorPoolByTypeService.java @@ -1,7 +1,7 @@ package ch.unisg.executorpool.application.service; -import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeQuery; -import ch.unisg.executorpool.application.port.in.GetAllExecutorInExecutorPoolByTypeUseCase; +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeQuery; +import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeUseCase; import ch.unisg.executorpool.domain.ExecutorClass; import ch.unisg.executorpool.domain.ExecutorPool; import lombok.RequiredArgsConstructor; @@ -13,10 +13,10 @@ import java.util.List; @RequiredArgsConstructor @Component @Transactional -public class GetAllExecutorInExecutorPoolByTypeService implements GetAllExecutorInExecutorPoolByTypeUseCase { +public class GetAllExecutorsInExecutorPoolByTypeService implements GetAllExecutorsInExecutorPoolByTypeUseCase { @Override - public List getAllExecutorInExecutorPoolByType(GetAllExecutorInExecutorPoolByTypeQuery query){ + public List getAllExecutorsInExecutorPoolByType(GetAllExecutorsInExecutorPoolByTypeQuery query){ ExecutorPool executorPool = ExecutorPool.getExecutorPool(); return executorPool.getAllExecutorsByType(query.getExecutorTaskType()); } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java index f865f4c..35685a3 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/DeleteTaskService.java @@ -5,6 +5,7 @@ import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskCommand; import ch.unisg.tapastasks.tasks.application.port.in.DeleteTaskUseCase; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.TaskList; +import jdk.jshell.spi.ExecutionControl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -23,11 +24,10 @@ public class DeleteTaskService implements DeleteTaskUseCase { Optional updatedTask = taskList.retrieveTaskById(command.getTaskId()); Task newTask = updatedTask.get(); // TODO: Fill in the right condition into the if-statement and the else-statement - if (/*the task can be deleted*/){ + if (true){ return taskList.deleteTaskById(command.getTaskId()); - } else { - /*send message back to TaskList that the task cannot be deleted*/ } - + // TODO Handle with a return message + return Optional.empty(); } } -- 2.45.1 From ec8ff4b3bac9fba1de12e72698acf8d62b5a0354 Mon Sep 17 00:00:00 2001 From: reynisson Date: Tue, 9 Nov 2021 22:56:11 +0100 Subject: [PATCH 49/94] Changed ExecutorIp and ExecutorPort to ExecutorUri. Also made all necessary changes for it to work --- .../formats/ExecutorJsonRepresentation.java | 51 +++++++++++++++++++ ...ewExecutorToExecutorPoolWebController.java | 13 +++-- .../adapter/in/web/ExecutorMediaType.java | 38 -------------- ...torsInExecutorPoolByTypeWebController.java | 5 +- ...lExecutorsInExecutorPoolWebController.java | 5 +- ...ExecutorFromExecutorPoolWebController.java | 14 +++-- .../AddNewExecutorToExecutorPoolCommand.java | 13 ++--- ...RemoveExecutorFromExecutorPoolCommand.java | 14 ++--- .../AddNewExecutorToExecutorPoolService.java | 2 +- ...RemoveExecutorFromExecutorPoolService.java | 2 +- .../executorpool/domain/ExecutorClass.java | 27 ++++------ .../executorpool/domain/ExecutorPool.java | 18 +++---- 12 files changed, 103 insertions(+), 99 deletions(-) create mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/formats/ExecutorJsonRepresentation.java delete mode 100644 executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/formats/ExecutorJsonRepresentation.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/formats/ExecutorJsonRepresentation.java new file mode 100644 index 0000000..3c8f6e4 --- /dev/null +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/common/formats/ExecutorJsonRepresentation.java @@ -0,0 +1,51 @@ +package ch.unisg.executorpool.adapter.common.formats; + +import ch.unisg.executorpool.domain.ExecutorClass; +import lombok.Getter; +import lombok.Setter; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.List; + +public class ExecutorJsonRepresentation { + public static final String EXECUTOR_MEDIA_TYPE = "application/json"; + + @Getter @Setter + private String executorUri; + + @Getter @Setter + private String executorTaskType; + + // TODO Check if this need Setters. Also applies to AuctionJsonRepresentation + public ExecutorJsonRepresentation(String executorUri, String executorTaskType){ + this.executorUri = executorUri; + this.executorTaskType = executorTaskType; + } + + public static String serialize(ExecutorClass executorClass) { + JSONObject payload = new JSONObject(); + + payload.put("executorUri", executorClass.getExecutorUri().getValue()); + payload.put("executorTaskType", executorClass.getExecutorTaskType().getValue()); + + return payload.toString(); + } + + public static String serialize(List listOfExecutors) { + JSONArray jsonArray = new JSONArray(); + + for (ExecutorClass executor: listOfExecutors) { + JSONObject jsonObject = new JSONObject(); + + jsonObject.put("executorUri", executor.getExecutorUri().getValue()); + jsonObject.put("executorTaskType", executor.getExecutorTaskType().getValue()); + + jsonArray.put(jsonObject); + } + + return jsonArray.toString(); + } + + private ExecutorJsonRepresentation() { } +} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java index 7967b6b..5a2dc09 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/AddNewExecutorToExecutorPoolWebController.java @@ -1,5 +1,6 @@ package ch.unisg.executorpool.adapter.in.web; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolUseCase; import ch.unisg.executorpool.application.port.in.AddNewExecutorToExecutorPoolCommand; import ch.unisg.executorpool.domain.ExecutorClass; @@ -11,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import javax.validation.ConstraintViolationException; +import java.net.URI; @RestController public class AddNewExecutorToExecutorPoolWebController { @@ -20,19 +22,20 @@ public class AddNewExecutorToExecutorPoolWebController { this.addNewExecutorToExecutorPoolUseCase = addNewExecutorToExecutorPoolUseCase; } - @PostMapping(path = "/executor-pool/AddExecutor", consumes = {ExecutorMediaType.EXECUTOR_MEDIA_TYPE}) - public ResponseEntity addNewExecutorToExecutorPool(@RequestBody ExecutorClass executorClass){ + @PostMapping(path = "/executor-pool/AddExecutor", consumes = {ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE}) + public ResponseEntity addNewExecutorToExecutorPool(@RequestBody ExecutorJsonRepresentation payload){ try{ AddNewExecutorToExecutorPoolCommand command = new AddNewExecutorToExecutorPoolCommand( - executorClass.getExecutorIp(), executorClass.getExecutorPort(), executorClass.getExecutorTaskType() + new ExecutorClass.ExecutorUri(URI.create(payload.getExecutorUri())), + new ExecutorClass.ExecutorTaskType(payload.getExecutorTaskType()) ); ExecutorClass newExecutor = addNewExecutorToExecutorPoolUseCase.addNewExecutorToExecutorPool(command); HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE); - return new ResponseEntity<>(ExecutorMediaType.serialize(newExecutor), responseHeaders, HttpStatus.CREATED); + return new ResponseEntity<>(ExecutorJsonRepresentation.serialize(newExecutor), responseHeaders, HttpStatus.CREATED); } catch (ConstraintViolationException e){ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java deleted file mode 100644 index 0ca4e1f..0000000 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/ExecutorMediaType.java +++ /dev/null @@ -1,38 +0,0 @@ -package ch.unisg.executorpool.adapter.in.web; - -import ch.unisg.executorpool.domain.ExecutorClass; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.List; - -final public class ExecutorMediaType { - public static final String EXECUTOR_MEDIA_TYPE = "application/json"; - - public static String serialize(ExecutorClass executorClass) { - JSONObject payload = new JSONObject(); - - payload.put("executorIp", executorClass.getExecutorIp().getValue()); - payload.put("executorPort", executorClass.getExecutorPort().getValue()); - payload.put("executorTaskType", executorClass.getExecutorTaskType().getValue()); - - return payload.toString(); - } - - public static String serialize(List listOfExecutors) { - String serializedList = "[ \n"; - - for (ExecutorClass executor: listOfExecutors) { - serializedList += serialize(executor) + "\n"; - } - - // return serializedList + "\n ]"; - JSONArray jsonArray = new JSONArray(); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("executorIp", "localhost"); - jsonArray.put(jsonObject); - return jsonArray.toString(); - } - - private ExecutorMediaType() { } -} diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java index dbea300..8c7ce3d 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolByTypeWebController.java @@ -1,5 +1,6 @@ package ch.unisg.executorpool.adapter.in.web; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeQuery; import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolByTypeUseCase; import ch.unisg.executorpool.domain.ExecutorClass; @@ -28,8 +29,8 @@ public class GetAllExecutorsInExecutorPoolByTypeWebController { // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE); - return new ResponseEntity<>(ExecutorMediaType.serialize(matchedExecutors), responseHeaders, HttpStatus.OK); + return new ResponseEntity<>(ExecutorJsonRepresentation.serialize(matchedExecutors), responseHeaders, HttpStatus.OK); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java index 70a5fd2..13a631a 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/GetAllExecutorsInExecutorPoolWebController.java @@ -1,5 +1,6 @@ package ch.unisg.executorpool.adapter.in.web; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; import ch.unisg.executorpool.application.port.in.GetAllExecutorsInExecutorPoolUseCase; import ch.unisg.executorpool.domain.ExecutorClass; import org.springframework.http.HttpHeaders; @@ -24,8 +25,8 @@ public class GetAllExecutorsInExecutorPoolWebController { // Add the content type as a response header HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE); - return new ResponseEntity<>(ExecutorMediaType.serialize(executorClassList), responseHeaders, HttpStatus.OK); + return new ResponseEntity<>(ExecutorJsonRepresentation.serialize(executorClassList), responseHeaders, HttpStatus.OK); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java index 69bbde3..28c3511 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/adapter/in/web/RemoveExecutorFromExecutorPoolWebController.java @@ -1,5 +1,6 @@ package ch.unisg.executorpool.adapter.in.web; +import ch.unisg.executorpool.adapter.common.formats.ExecutorJsonRepresentation; import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolCommand; import ch.unisg.executorpool.application.port.in.RemoveExecutorFromExecutorPoolUseCase; import ch.unisg.executorpool.domain.ExecutorClass; @@ -11,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; +import java.net.URI; import java.util.Optional; @RestController @@ -21,9 +23,11 @@ public class RemoveExecutorFromExecutorPoolWebController { this.removeExecutorFromExecutorPoolUseCase = removeExecutorFromExecutorPoolUseCase; } - @PostMapping(path = "/executor-pool/RemoveExecutor", consumes = {ExecutorMediaType.EXECUTOR_MEDIA_TYPE}) - public ResponseEntity removeExecutorFromExecutorPool(@RequestBody ExecutorClass executorClass){ - RemoveExecutorFromExecutorPoolCommand command = new RemoveExecutorFromExecutorPoolCommand(executorClass.getExecutorIp(), executorClass.getExecutorPort()); + @PostMapping(path = "/executor-pool/RemoveExecutor", consumes = {ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE}) + public ResponseEntity removeExecutorFromExecutorPool(@RequestBody ExecutorJsonRepresentation executorJsonRepresentation){ + RemoveExecutorFromExecutorPoolCommand command = new RemoveExecutorFromExecutorPoolCommand( + new ExecutorClass.ExecutorUri(URI.create(executorJsonRepresentation.getExecutorUri())) + ); Optional removedExecutor = removeExecutorFromExecutorPoolUseCase.removeExecutorFromExecutorPool(command); if(removedExecutor.isEmpty()){ @@ -31,9 +35,9 @@ public class RemoveExecutorFromExecutorPoolWebController { } HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorMediaType.EXECUTOR_MEDIA_TYPE); + responseHeaders.add(HttpHeaders.CONTENT_TYPE, ExecutorJsonRepresentation.EXECUTOR_MEDIA_TYPE); - return new ResponseEntity<>(ExecutorMediaType.serialize(removedExecutor.get()), responseHeaders, + return new ResponseEntity<>(ExecutorJsonRepresentation.serialize(removedExecutor.get()), responseHeaders, HttpStatus.OK); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java index 2682610..ddd7da9 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/AddNewExecutorToExecutorPoolCommand.java @@ -2,8 +2,7 @@ package ch.unisg.executorpool.application.port.in; import ch.unisg.common.SelfValidating; import ch.unisg.executorpool.domain.ExecutorPool; -import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; -import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorUri; import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; import lombok.Value; import javax.validation.constraints.NotNull; @@ -11,17 +10,13 @@ import javax.validation.constraints.NotNull; @Value public class AddNewExecutorToExecutorPoolCommand extends SelfValidating { @NotNull - private final ExecutorIp executorIp; - - @NotNull - private final ExecutorPort executorPort; + private final ExecutorUri executorUri; @NotNull private final ExecutorTaskType executorTaskType; - public AddNewExecutorToExecutorPoolCommand(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ - this.executorIp = executorIp; - this.executorPort = executorPort; + public AddNewExecutorToExecutorPoolCommand(ExecutorUri executorUri, ExecutorTaskType executorTaskType){ + this.executorUri = executorUri; this.executorTaskType = executorTaskType; this.validateSelf(); } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java index 11763a9..162426c 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/port/in/RemoveExecutorFromExecutorPoolCommand.java @@ -1,9 +1,7 @@ package ch.unisg.executorpool.application.port.in; -import ch.unisg.executorpool.domain.ExecutorClass; import ch.unisg.common.SelfValidating; -import ch.unisg.executorpool.domain.ExecutorClass.ExecutorIp; -import ch.unisg.executorpool.domain.ExecutorClass.ExecutorPort; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorUri; import lombok.Value; import javax.validation.constraints.NotNull; @@ -11,14 +9,10 @@ import javax.validation.constraints.NotNull; @Value public class RemoveExecutorFromExecutorPoolCommand extends SelfValidating { @NotNull - private final ExecutorIp executorIp; + private final ExecutorUri executorUri; - @NotNull - private final ExecutorPort executorPort; - - public RemoveExecutorFromExecutorPoolCommand(ExecutorIp executorIp, ExecutorPort executorPort){ - this.executorIp = executorIp; - this.executorPort = executorPort; + public RemoveExecutorFromExecutorPoolCommand(ExecutorUri executorUri){ + this.executorUri = executorUri; this.validateSelf(); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java index e1ef237..200739b 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/AddNewExecutorToExecutorPoolService.java @@ -20,6 +20,6 @@ public class AddNewExecutorToExecutorPoolService implements AddNewExecutorToExec public ExecutorClass addNewExecutorToExecutorPool(AddNewExecutorToExecutorPoolCommand command){ ExecutorPool executorPool = ExecutorPool.getExecutorPool(); - return executorPool.addNewExecutor(command.getExecutorIp(), command.getExecutorPort(), command.getExecutorTaskType()); + return executorPool.addNewExecutor(command.getExecutorUri(), command.getExecutorTaskType()); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java index 639ba7f..a606f57 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/application/service/RemoveExecutorFromExecutorPoolService.java @@ -17,6 +17,6 @@ public class RemoveExecutorFromExecutorPoolService implements RemoveExecutorFrom @Override public Optional removeExecutorFromExecutorPool(RemoveExecutorFromExecutorPoolCommand command){ ExecutorPool executorPool = ExecutorPool.getExecutorPool(); - return executorPool.removeExecutorByIpAndPort(command.getExecutorIp(), command.getExecutorPort()); + return executorPool.removeExecutorByIpAndPort(command.getExecutorUri()); } } diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java index d1fca00..5da6fe7 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorClass.java @@ -3,36 +3,29 @@ package ch.unisg.executorpool.domain; import lombok.Getter; import lombok.Value; +import java.net.URI; + public class ExecutorClass { @Getter - private final ExecutorIp executorIp; - - @Getter - private final ExecutorPort executorPort; + private final ExecutorUri executorUri; @Getter private final ExecutorTaskType executorTaskType; - public ExecutorClass(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ - this.executorIp = executorIp; - this.executorPort = executorPort; + public ExecutorClass(ExecutorUri executorUri, ExecutorTaskType executorTaskType){ + this.executorUri = executorUri; this.executorTaskType = executorTaskType; } - protected static ExecutorClass createExecutorClass(ExecutorIp executorIp, ExecutorPort executorPort, ExecutorTaskType executorTaskType){ - System.out.println("New Task: " + executorIp.getValue() + " " + executorPort.getValue() + " " + executorTaskType.getValue()); - return new ExecutorClass(executorIp, executorPort, executorTaskType); + protected static ExecutorClass createExecutorClass(ExecutorUri executorUri, ExecutorTaskType executorTaskType){ + System.out.println("New Executor: " + executorUri.value.toString() + " " + executorTaskType.getValue()); + return new ExecutorClass(executorUri, executorTaskType); } @Value - public static class ExecutorIp { - private String value; - } - - @Value - public static class ExecutorPort { - private String value; + public static class ExecutorUri { + private URI value; } @Value diff --git a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java index dd5375b..0ca0d5e 100644 --- a/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java +++ b/executor-pool/src/main/java/ch/unisg/executorpool/domain/ExecutorPool.java @@ -1,5 +1,8 @@ package ch.unisg.executorpool.domain; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorUri; +import ch.unisg.executorpool.domain.ExecutorClass.ExecutorTaskType; + import lombok.Getter; import lombok.Value; @@ -20,19 +23,17 @@ public class ExecutorPool { public static ExecutorPool getExecutorPool() { return executorPool; } - public ExecutorClass addNewExecutor(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort, ExecutorClass.ExecutorTaskType executorTaskType){ - ExecutorClass newExecutor = ExecutorClass.createExecutorClass(executorIp, executorPort, executorTaskType); + public ExecutorClass addNewExecutor(ExecutorUri executorUri, ExecutorTaskType executorTaskType){ + ExecutorClass newExecutor = ExecutorClass.createExecutorClass(executorUri, executorTaskType); listOfExecutors.value.add(newExecutor); System.out.println("Number of executors: " + listOfExecutors.value.size()); return newExecutor; } - public Optional getExecutorByIpAndPort(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort){ + public Optional getExecutorByUri(ExecutorUri executorUri){ for (ExecutorClass executor : listOfExecutors.value ) { - // TODO can this be simplified by overwriting equals()? - if(executor.getExecutorIp().getValue().equalsIgnoreCase(executorIp.getValue()) && - executor.getExecutorPort().getValue().equalsIgnoreCase(executorPort.getValue())){ + if(executor.getExecutorUri().getValue().equals(executorUri)){ return Optional.of(executor); } } @@ -54,11 +55,10 @@ public class ExecutorPool { return matchedExecutors; } - public Optional removeExecutorByIpAndPort(ExecutorClass.ExecutorIp executorIp, ExecutorClass.ExecutorPort executorPort){ + public Optional removeExecutorByIpAndPort(ExecutorUri executorUri){ for (ExecutorClass executor : listOfExecutors.value ) { // TODO can this be simplified by overwriting equals()? - if(executor.getExecutorIp().getValue().equalsIgnoreCase(executorIp.getValue()) && - executor.getExecutorPort().getValue().equalsIgnoreCase(executorPort.getValue())){ + if(executor.getExecutorUri().getValue().equals(executorUri.getValue())){ listOfExecutors.value.remove(executor); return Optional.of(executor); } -- 2.45.1 From 74a51cfcf681f8acd804eef2b24dc341e61e419b Mon Sep 17 00:00:00 2001 From: "julius.lautz" Date: Wed, 10 Nov 2021 11:03:57 +0000 Subject: [PATCH 50/94] changed deadline to timestamp and the logic to schedule an auction --- .../application/port/in/LaunchAuctionCommand.java | 5 +++-- .../application/service/StartAuctionService.java | 3 ++- .../java/ch/unisg/tapas/auctionhouse/domain/Auction.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java index 626fa49..37eb5db 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/port/in/LaunchAuctionCommand.java @@ -1,10 +1,11 @@ package ch.unisg.tapas.auctionhouse.application.port.in; import ch.unisg.tapas.auctionhouse.domain.Auction; -import ch.unisg.tapas.common.SelfValidating; +import ch.unisg.common.SelfValidating; +import lombok.NonNull; import lombok.Value; -import javax.validation.constraints.NotNull; +import javax.validation.constraint.NotNull; /** * Command for launching an auction in this auction house. diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java index 42c6e37..b9d9d3d 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/application/service/StartAuctionService.java @@ -2,6 +2,7 @@ package ch.unisg.tapas.auctionhouse.application.service; import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionCommand; import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase; +import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase; import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort; import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort; import ch.unisg.tapas.auctionhouse.domain.*; @@ -63,7 +64,7 @@ public class StartAuctionService implements LaunchAuctionUseCase { auctions.addAuction(auction); // Schedule the closing of the auction at the deadline - service.schedule(new CloseAuctionTask(auction.getAuctionId()), deadline.getValue(), + service.schedule(new CloseAuctionTask(auction.getAuctionId()), deadline.getValue().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); // Publish an auction started event diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java index 3e51ef7..c6d9333 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/domain/Auction.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Value; import java.net.URI; +import java.sql.Timestamp; import java.util.*; /** @@ -166,6 +167,6 @@ public class Auction { @Value public static class AuctionDeadline { - int value; + Timestamp value; } } -- 2.45.1 From 18fdf819f1d45e5cc1d45a5ec41c557b12e5ad24 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 10 Nov 2021 15:14:41 +0100 Subject: [PATCH 51/94] First implementation of WebSub --- mocks/README.md | 29 +++++++ mocks/auction-house/auctions.js | 39 +++++++++ mocks/auction-house/publisher.js | 17 ++++ mocks/auction-house/subscriber.js | 42 +++++++++ tapas-auction-house/pom.xml | 11 +++ .../tapas/TapasAuctionHouseApplication.java | 11 ++- .../common/clients/WebSubSubscriber.java | 85 +++++++++++++++++++ ...tionStartedEventListenerWebSubAdapter.java | 21 ++++- .../websub/ValidateIntentWebSubAdapter.java | 36 ++++++++ ...blishAuctionStartedEventWebSubAdapter.java | 38 ++++++++- .../src/main/resources/application.properties | 4 + 11 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 mocks/README.md create mode 100644 mocks/auction-house/auctions.js create mode 100644 mocks/auction-house/publisher.js create mode 100644 mocks/auction-house/subscriber.js create mode 100644 tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java diff --git a/mocks/README.md b/mocks/README.md new file mode 100644 index 0000000..7135682 --- /dev/null +++ b/mocks/README.md @@ -0,0 +1,29 @@ +In this directory are some files to mock an auction house to test WebSub local. + +To run a local WebSubHub instance + +1. Start a mongodb in docker: + +- docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no tutum/mongodb + +2. Install a local hub + +- yarn global add websub-hub + +3. Run the hub localy + +- websub-hub -l info -m mongodb://localhost:27017/hub + +Create an example subscription + +- node auction-house/subscriber.js + +Create an example auctionhouse + +- node auction-house/auctions.js + +Publish to the hub + +- node auction-house/publisher.js + +Mostly inspired by: https://github.com/hemerajs/websub-hub diff --git a/mocks/auction-house/auctions.js b/mocks/auction-house/auctions.js new file mode 100644 index 0000000..68a6aa5 --- /dev/null +++ b/mocks/auction-house/auctions.js @@ -0,0 +1,39 @@ +// Require the framework and instantiate it +const fastify = require('fastify')({ logger: true }) + +// Declare a route +fastify.get('/auctions', async (request, reply) => { + console.log('content provided') + + return [ + { + id: '2', + content_text: 'This is a second item.', + url: 'https://example.org/second-item' + }, + { + id: '1', + content_html: '