diff --git a/.gitignore b/.gitignore index 216783d..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +0,0 @@ -# Gradle -.gradle/ -build/ - -# Eclipse -.project -.classpath -.settings/ -bin/ - -# IntelliJ -.idea -*.ipr -*.iml -*.iws - -# NetBeans -nb-configuration.xml - -# Visual Studio Code -.vscode -.factorypath - -# OSX -.DS_Store - -# Vim -*.swp -*.swo - -# patch -*.orig -*.rej - -# Local environment -.env - -# Plugin directory -/.quarkus/cli/plugins/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..78ead78 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,47 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..d20b597 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..6d0ee1c --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3fc909c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..23fe160 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/m1_pe_kafka.iml b/.idea/modules/m1_pe_kafka.iml new file mode 100644 index 0000000..bd41dfe --- /dev/null +++ b/.idea/modules/m1_pe_kafka.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.run/Compose.run.xml b/.run/Compose.run.xml new file mode 100644 index 0000000..fb9dc64 --- /dev/null +++ b/.run/Compose.run.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.run/compose.run.xml b/.run/compose.run.xml deleted file mode 100644 index d9fb245..0000000 --- a/.run/compose.run.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 94d59e9..556aabf 100644 --- a/README.md +++ b/README.md @@ -23,26 +23,26 @@ Alternatively, you can build and run the project manually by using [gradle](https://gradle.org/) and launching the application with [docker-compose](https://docs.docker.com/compose/). ```shell -gradlew build +# build the modules +(cd ./applications/consumer/ && ./gradlew build) +(cd ./applications/converter/ && ./gradlew build) +(cd ./applications/producer/ && ./gradlew build) +# start the docker based on the docker-compose.yaml file docker compose up ``` ## Configuration -If you wish, you can modify the configuration of the `docker-compose.yaml` file to fit your needs. +The configuration can be modified in the [docker-compose.yaml](./docker-compose.yaml) file to fit your needs. -The container `application` can be easily modified with the following environment variables : - -| Name | Required | Format | Default | Description | -|------------------------------|----------|-----------------------------------------------------|--------------------------------------------------------------------|---------------------------------------------------------------| -| KAFKA_BOOTSTRAP_SERVERS | true | \[:port] | / | The Kafka server address | -| TOPIC_TEMPERATURE_CELSIUS | false | string of alphanumeric characters, ".", "_" and "-" | temperature-celsius | The name of the Kafka topic for the temperature in Celsius | -| TOPIC_TEMPERATURE_FAHRENHEIT | false | string of alphanumeric characters, ".", "_" and "-" | temperature-fahrenheit | The name of the Kafka topic for the temperature in Fahrenheit | -| TEMPERATURE_LOCATION | true | \, \ | 49.9, 2.3 ([Amiens, France](https://fr.wikipedia.org/wiki/Amiens)) | The coordinates where to get the temperatures from | +You can find a list for each module of the project containing their individual configuration : +- [producer](./applications/producer/README.md) +- [consumer](./applications/consumer/README.md) +- [converter](./applications/converter/README.md) ## Expectation The `application` container shall print the current temperature at the selected place in Fahrenheit every minute. -You can also access this value with the REST api at the `http://localhost:8080/temperature` endpoint. +You can also access this value with the REST api at the `http://localhost:8080/v1/temperature` endpoint. ## References -The project use the [Open-Meteo API](https://open-meteo.com/) to fetch the current temperature at the -given location. \ No newline at end of file +The project use the [Open-Meteo](https://open-meteo.com/) API to fetch the current temperature at the +given location. diff --git a/applications/common/.gitignore b/applications/common/.gitignore new file mode 100644 index 0000000..216783d --- /dev/null +++ b/applications/common/.gitignore @@ -0,0 +1,39 @@ +# Gradle +.gradle/ +build/ + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/applications/common/README.md b/applications/common/README.md new file mode 100644 index 0000000..b0defed --- /dev/null +++ b/applications/common/README.md @@ -0,0 +1,15 @@ +# Module: Common + +This module contain common code that could be used between the three others modules of the application. + +It implements some errors or some useful functionalities and structure to avoid redefining them in all the modules +and making debugging easier by only modifying a single file to fix the corresponding issue everywhere. + +## Build + +This project is built automatically when building the others modules. + +However, you can still build it manually with the following command : +```shell +./gradlew build +``` diff --git a/build.gradle.kts b/applications/common/build.gradle.kts similarity index 87% rename from build.gradle.kts rename to applications/common/build.gradle.kts index 1468849..1d82bad 100644 --- a/build.gradle.kts +++ b/applications/common/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.9.23" - kotlin("plugin.allopen") version "1.9.23" + kotlin("jvm") version "2.0.0" + kotlin("plugin.allopen") version "2.0.0" id("io.quarkus") } @@ -14,9 +14,6 @@ val quarkusPlatformArtifactId: String by project val quarkusPlatformVersion: String by project dependencies { - // language - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - // quarkus implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) implementation("io.quarkus:quarkus-kotlin") @@ -27,7 +24,6 @@ dependencies { implementation("io.quarkus:quarkus-kafka-client") implementation("io.quarkus:quarkus-kafka-streams") implementation("io.quarkus:quarkus-messaging-kafka") - implementation("io.quarkus:quarkus-rest") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.8.9") } diff --git a/applications/common/gradle.properties b/applications/common/gradle.properties new file mode 100644 index 0000000..a890bb8 --- /dev/null +++ b/applications/common/gradle.properties @@ -0,0 +1,6 @@ +#Gradle properties +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.12.0 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.12.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/applications/common/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to applications/common/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/applications/common/gradle/wrapper/gradle-wrapper.properties similarity index 93% rename from gradle/wrapper/gradle-wrapper.properties rename to applications/common/gradle/wrapper/gradle-wrapper.properties index 17655d0..0d18421 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/applications/common/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/applications/common/gradlew similarity index 100% rename from gradlew rename to applications/common/gradlew diff --git a/gradlew.bat b/applications/common/gradlew.bat similarity index 100% rename from gradlew.bat rename to applications/common/gradlew.bat diff --git a/settings.gradle.kts b/applications/common/settings.gradle.kts similarity index 89% rename from settings.gradle.kts rename to applications/common/settings.gradle.kts index 54afb3f..07e5267 100644 --- a/settings.gradle.kts +++ b/applications/common/settings.gradle.kts @@ -10,4 +10,4 @@ pluginManagement { id(quarkusPluginId) version quarkusPluginVersion } } -rootProject.name = "m1_pe_kafka" +rootProject.name = "common" diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/error/MissingEnvironmentException.kt b/applications/common/src/main/kotlin/fr/faraphel/common/error/MissingEnvironmentException.kt similarity index 84% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/error/MissingEnvironmentException.kt rename to applications/common/src/main/kotlin/fr/faraphel/common/error/MissingEnvironmentException.kt index 9ea00a1..2d328d6 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/error/MissingEnvironmentException.kt +++ b/applications/common/src/main/kotlin/fr/faraphel/common/error/MissingEnvironmentException.kt @@ -1,4 +1,4 @@ -package fr.faraphel.m1_pe_kafka.error +package fr.faraphel.common.error /** diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/error/http/HttpException.kt b/applications/common/src/main/kotlin/fr/faraphel/common/error/http/HttpException.kt similarity index 88% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/error/http/HttpException.kt rename to applications/common/src/main/kotlin/fr/faraphel/common/error/http/HttpException.kt index 8ef9daf..4fb47aa 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/error/http/HttpException.kt +++ b/applications/common/src/main/kotlin/fr/faraphel/common/error/http/HttpException.kt @@ -1,4 +1,4 @@ -package fr.faraphel.m1_pe_kafka.error.http +package fr.faraphel.common.error.http import java.io.IOException diff --git a/applications/common/src/main/kotlin/fr/faraphel/common/temperature/Temperature.kt b/applications/common/src/main/kotlin/fr/faraphel/common/temperature/Temperature.kt new file mode 100644 index 0000000..77e567e --- /dev/null +++ b/applications/common/src/main/kotlin/fr/faraphel/common/temperature/Temperature.kt @@ -0,0 +1,21 @@ +package fr.faraphel.common.temperature + + +/** + * Represent a temperature + * Allow to store temperature and convert the units in an easier way + * @param value the value of the temperature (Kelvin) + */ +class Temperature( + private val value: Double +) { + // convert Temperature to Double (Kelvin) + val asKelvin: Double + get() = this.value + // convert Temperature to Double (Celsius) + val asCelsius: Double + get() = this.value - 275.15 + // convert Temperature to Double (Fahrenheit) + val asFahrenheit: Double + get() = ((this.value - 273.15) * 1.8) + 32 +} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/utils/Temperature.kt b/applications/common/src/main/kotlin/fr/faraphel/common/temperature/conversion.kt similarity index 53% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/utils/Temperature.kt rename to applications/common/src/main/kotlin/fr/faraphel/common/temperature/conversion.kt index c06da1b..a307d59 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/utils/Temperature.kt +++ b/applications/common/src/main/kotlin/fr/faraphel/common/temperature/conversion.kt @@ -1,25 +1,6 @@ -package fr.faraphel.m1_pe_kafka.utils +package fr.faraphel.common.temperature -/** - * Represent a temperature - * Allow to store temperature and convert the units in an easier way - * @param value the value of the temperature (Kelvin) - */ -class Temperature( - private val value: Double -) { - // convert Temperature to Double (Kelvin) - val asKelvin: Double - get() = this.value - // convert Temperature to Double (Celsius) - val asCelsius: Double - get() = this.value - 275.15 - // convert Temperature to Double (Fahrenheit) - val asFahrenheit: Double - get() = ((this.value - 273.15) * 1.8) + 32 -} - // convert Double (Kelvin) to Temperature val Double.kelvin: Temperature get() = Temperature(this) diff --git a/applications/common/src/main/resources/application.properties b/applications/common/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/.dockerignore b/applications/consumer/.dockerignore similarity index 100% rename from .dockerignore rename to applications/consumer/.dockerignore diff --git a/applications/consumer/.gitignore b/applications/consumer/.gitignore new file mode 100644 index 0000000..216783d --- /dev/null +++ b/applications/consumer/.gitignore @@ -0,0 +1,39 @@ +# Gradle +.gradle/ +build/ + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/applications/consumer/README.md b/applications/consumer/README.md new file mode 100644 index 0000000..2d16e3e --- /dev/null +++ b/applications/consumer/README.md @@ -0,0 +1,22 @@ +# Module: Consumer + +This module contain the code responsible for receiving values, displaying them in the container output and hosting +the REST API where we can find the latest value received. + +## Build + +This project is built automatically when using [docker-compose](https://docs.docker.com/compose/) at the root. + +However, you can still build it manually with the following command : +```shell +./gradlew build +``` + +## Configuration + +This module can be easily modified with the following environment variables : + +| Name | Required | Format | Default | Description | +|-------------------------|----------|-----------------------------------------------------|---------|---------------------------------------------------| +| KAFKA_BOOTSTRAP_SERVERS | true | \[:port] | / | The Kafka server address | +| TEMPERATURE_TOPIC | true | string of alphanumeric characters, ".", "_" and "-" | / | The Kafka topic were the temperature can be found | diff --git a/applications/consumer/build.gradle.kts b/applications/consumer/build.gradle.kts new file mode 100644 index 0000000..b937130 --- /dev/null +++ b/applications/consumer/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + kotlin("jvm") version "2.0.0" + kotlin("plugin.allopen") version "2.0.0" + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +dependencies { + // common + implementation(project(":common")) + + // quarkus + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-arc") + testImplementation("io.quarkus:quarkus-junit5") + implementation("io.quarkus:quarkus-rest") + + // libraries + implementation("io.quarkus:quarkus-kafka-client") + implementation("io.quarkus:quarkus-kafka-streams") + implementation("com.google.code.gson:gson:2.8.9") +} + +group = "fr.faraphel" +version = "1.0-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") +} +allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("jakarta.persistence.Entity") + annotation("io.quarkus.test.junit.QuarkusTest") +} + +tasks.withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_21.toString() + kotlinOptions.javaParameters = true +} diff --git a/applications/consumer/gradle.properties b/applications/consumer/gradle.properties new file mode 100644 index 0000000..a890bb8 --- /dev/null +++ b/applications/consumer/gradle.properties @@ -0,0 +1,6 @@ +#Gradle properties +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.12.0 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.12.0 \ No newline at end of file diff --git a/applications/consumer/gradle/wrapper/gradle-wrapper.jar b/applications/consumer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/applications/consumer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/applications/consumer/gradle/wrapper/gradle-wrapper.properties b/applications/consumer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0d18421 --- /dev/null +++ b/applications/consumer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/applications/consumer/gradlew b/applications/consumer/gradlew new file mode 100755 index 0000000..fbd7c51 --- /dev/null +++ b/applications/consumer/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +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 +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/applications/consumer/gradlew.bat b/applications/consumer/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/applications/consumer/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/applications/consumer/settings.gradle.kts b/applications/consumer/settings.gradle.kts new file mode 100644 index 0000000..6624e23 --- /dev/null +++ b/applications/consumer/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + val quarkusPluginVersion: String by settings + val quarkusPluginId: String by settings + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id(quarkusPluginId) version quarkusPluginVersion + } +} +rootProject.name = "consumer" + +// Dependencies +include(":common") +project(":common").projectDir = file("../common") \ No newline at end of file diff --git a/src/main/docker/Dockerfile.jvm b/applications/consumer/src/main/docker/Dockerfile.jvm similarity index 95% rename from src/main/docker/Dockerfile.jvm rename to applications/consumer/src/main/docker/Dockerfile.jvm index e1f276f..ac50743 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/applications/consumer/src/main/docker/Dockerfile.jvm @@ -7,11 +7,11 @@ # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/m1_pe_kafka-jvm . +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/consumer-jvm . # # Then run the container using: # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka-jvm +# docker run -i --rm -p 8080:8080 quarkus/consumer-jvm # # If you want to include the debug port into your docker image # you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. @@ -20,7 +20,7 @@ # # Then run the container using : # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka-jvm +# docker run -i --rm -p 8080:8080 quarkus/consumer-jvm # # This image uses the `run-java.sh` script to run the application. # This scripts computes the command line to execute your Java application, and @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-21:1.18 +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 ENV LANGUAGE='en_US:en' diff --git a/src/main/docker/Dockerfile.legacy-jar b/applications/consumer/src/main/docker/Dockerfile.legacy-jar similarity index 95% rename from src/main/docker/Dockerfile.legacy-jar rename to applications/consumer/src/main/docker/Dockerfile.legacy-jar index 694acea..68f547f 100644 --- a/src/main/docker/Dockerfile.legacy-jar +++ b/applications/consumer/src/main/docker/Dockerfile.legacy-jar @@ -7,11 +7,11 @@ # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/m1_pe_kafka-legacy-jar . +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/consumer-legacy-jar . # # Then run the container using: # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka-legacy-jar +# docker run -i --rm -p 8080:8080 quarkus/consumer-legacy-jar # # If you want to include the debug port into your docker image # you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. @@ -20,7 +20,7 @@ # # Then run the container using : # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka-legacy-jar +# docker run -i --rm -p 8080:8080 quarkus/consumer-legacy-jar # # This image uses the `run-java.sh` script to run the application. # This scripts computes the command line to execute your Java application, and @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-21:1.18 +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 ENV LANGUAGE='en_US:en' diff --git a/src/main/docker/Dockerfile.native b/applications/consumer/src/main/docker/Dockerfile.native similarity index 81% rename from src/main/docker/Dockerfile.native rename to applications/consumer/src/main/docker/Dockerfile.native index 2071597..c4c22fd 100644 --- a/src/main/docker/Dockerfile.native +++ b/applications/consumer/src/main/docker/Dockerfile.native @@ -7,11 +7,11 @@ # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile.native -t quarkus/m1_pe_kafka . +# docker build -f src/main/docker/Dockerfile.native -t quarkus/consumer . # # Then run the container using: # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka +# docker run -i --rm -p 8080:8080 quarkus/consumer # ### FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 diff --git a/src/main/docker/Dockerfile.native-micro b/applications/consumer/src/main/docker/Dockerfile.native-micro similarity index 92% rename from src/main/docker/Dockerfile.native-micro rename to applications/consumer/src/main/docker/Dockerfile.native-micro index 703e0d6..47dbda9 100644 --- a/src/main/docker/Dockerfile.native-micro +++ b/applications/consumer/src/main/docker/Dockerfile.native-micro @@ -10,11 +10,11 @@ # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/m1_pe_kafka . +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/consumer . # # Then run the container using: # -# docker run -i --rm -p 8080:8080 quarkus/m1_pe_kafka +# docker run -i --rm -p 8080:8080 quarkus/consumer # ### FROM quay.io/quarkus/quarkus-micro-image:2.0 diff --git a/applications/consumer/src/main/kotlin/fr/faraphel/consumer/Main.kt b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/Main.kt new file mode 100644 index 0000000..39c1709 --- /dev/null +++ b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/Main.kt @@ -0,0 +1,41 @@ +package fr.faraphel.consumer + +import fr.faraphel.consumer.kafka.Consumer +import fr.faraphel.consumer.rest.TemperatureEndpoint +import fr.faraphel.common.error.MissingEnvironmentException +import io.quarkus.runtime.QuarkusApplication +import io.quarkus.runtime.annotations.QuarkusMain +import java.time.Instant + + +@QuarkusMain +class Main : QuarkusApplication { + override fun run(vararg args: String?): Int { + // get the kafka server address + val kafkaServer = System.getenv("KAFKA_BOOTSTRAP_SERVERS") + ?: throw MissingEnvironmentException("KAFKA_BOOTSTRAP_SERVERS") + + // get the topic name + val topicTemperature: String = System.getenv("TEMPERATURE_TOPIC") + ?: throw MissingEnvironmentException("TEMPERATURE_TOPIC") + + // create a consumer that will print the received values in the input topic + val consumer = Consumer( + server=kafkaServer, + topic=topicTemperature + ) { message -> + // clean the values in the record + val messageInstant = Instant.ofEpochMilli(message.timestamp()) + val messageContent = message.value() + // print the value + println("[${timeFormatter.format(messageInstant)}] ${messageContent}°F") + // update the value for the API + TemperatureEndpoint.updateData(messageContent, messageInstant) + } + + // indefinitely consume new values + consumer.consumeForever() + + return 0 + } +} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Consumer.kt b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/kafka/Consumer.kt similarity index 88% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Consumer.kt rename to applications/consumer/src/main/kotlin/fr/faraphel/consumer/kafka/Consumer.kt index ba318f8..dcf9139 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Consumer.kt +++ b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/kafka/Consumer.kt @@ -1,5 +1,4 @@ -package fr.faraphel.m1_pe_kafka.kafka - +package fr.faraphel.consumer.kafka import org.apache.kafka.clients.consumer.* import org.apache.kafka.common.serialization.DoubleDeserializer @@ -21,10 +20,10 @@ import kotlin.time.toJavaDuration */ class Consumer( private val server: String, - private val identifier: String = "consumer", + private val identifier: String = "fr/faraphel/consumer", private val topic: String, private val callback: (ConsumerRecord) -> Unit, -) : Thread() { +) { private val properties: Properties = Properties().apply { // identifier this[ConsumerConfig.GROUP_ID_CONFIG] = identifier @@ -49,7 +48,7 @@ class Consumer( val messages: ConsumerRecords = this.consumer.poll(timeout) // print them with their timestamp and content - messages.forEach { message -> this.callback(message) } + messages.forEach(this.callback) } /** @@ -59,10 +58,4 @@ class Consumer( fun consumeForever(timeout: Duration = 1.seconds.toJavaDuration()) { while (true) this.consume(timeout) } - - /** - * Thread entrypoint - * @see consumeForever - */ - override fun run() = this.consumeForever() } diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/PingEndpoint.kt b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/PingEndpoint.kt similarity index 86% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/PingEndpoint.kt rename to applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/PingEndpoint.kt index 03a93b8..10a73d4 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/PingEndpoint.kt +++ b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/PingEndpoint.kt @@ -1,4 +1,4 @@ -package fr.faraphel.m1_pe_kafka.rest +package fr.faraphel.consumer.rest import jakarta.ws.rs.GET import jakarta.ws.rs.Path @@ -9,7 +9,7 @@ import jakarta.ws.rs.Path * Always answer "Pong!" * Can be used to test if the API can be reached */ -@Path("ping") +@Path("/v1/ping") class PingEndpoint { /** * Handler for a GET request on this endpoint diff --git a/applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/TemperatureEndpoint.kt b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/TemperatureEndpoint.kt new file mode 100644 index 0000000..c80bca4 --- /dev/null +++ b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/rest/TemperatureEndpoint.kt @@ -0,0 +1,43 @@ +package fr.faraphel.consumer.rest + +import com.google.gson.Gson +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType +import java.time.Instant + + +/** + * This API endpoint return the latest temperature measured (in Fahrenheit) + */ +@Path("/v1/temperature") +class TemperatureEndpoint { + companion object { + val jsonParser = Gson() ///< the json parser + + private var temperature: Double? = null ///< the latest temperature value + private var time: Instant? = null /// < the time of the latest value + + /** + * Setter to update the latest temperature value + * @param temperature the new temperature value + */ + fun updateData(temperature: Double, time: Instant) { + this.temperature = temperature + this.time = time + } + } + + /** + * Handler for a GET request on this endpoint + * @return the latest temperature measured + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + fun get(): String = jsonParser.toJson(mapOf( + "value" to temperature, + "time" to time?.toEpochMilli(), + "unit" to "Fahrenheit" + )) +} diff --git a/applications/consumer/src/main/kotlin/fr/faraphel/consumer/utils.kt b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/utils.kt new file mode 100644 index 0000000..f310d7b --- /dev/null +++ b/applications/consumer/src/main/kotlin/fr/faraphel/consumer/utils.kt @@ -0,0 +1,10 @@ +package fr.faraphel.consumer + +import java.time.ZoneId +import java.time.format.DateTimeFormatter + + +// create a time formatter +val timeFormatter: DateTimeFormatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss.SSS") + .withZone(ZoneId.systemDefault()) diff --git a/applications/consumer/src/main/resources/application.properties b/applications/consumer/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/applications/converter/.dockerignore b/applications/converter/.dockerignore new file mode 100644 index 0000000..4361d2f --- /dev/null +++ b/applications/converter/.dockerignore @@ -0,0 +1,5 @@ +* +!build/*-runner +!build/*-runner.jar +!build/lib/* +!build/quarkus-app/* \ No newline at end of file diff --git a/applications/converter/.gitignore b/applications/converter/.gitignore new file mode 100644 index 0000000..216783d --- /dev/null +++ b/applications/converter/.gitignore @@ -0,0 +1,39 @@ +# Gradle +.gradle/ +build/ + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/applications/converter/README.md b/applications/converter/README.md new file mode 100644 index 0000000..617d602 --- /dev/null +++ b/applications/converter/README.md @@ -0,0 +1,23 @@ +# Module: Converter + +This module contain the code responsible for receiving values from a topic, converting them from +Celsius temperature to Fahrenheit and send it back into another topic. + +## Build + +This project is built automatically when using [docker-compose](https://docs.docker.com/compose/) at the root. + +However, you can still build it manually with the following command : +```shell +./gradlew build +``` + +## Configuration + +This module can be easily modified with the following environment variables : + +| Name | Required | Format | Default | Description | +|------------------------------|----------|-----------------------------------------------------|---------|-----------------------------------------------------------------| +| KAFKA_BOOTSTRAP_SERVERS | true | \[:port] | / | The Kafka server address | +| TEMPERATURE_CELSIUS_TOPIC | true | string of alphanumeric characters, ".", "_" and "-" | / | The Kafka topic were the temperature in celsius can be found | +| TEMPERATURE_FAHRENHEIT_TOPIC | true | string of alphanumeric characters, ".", "_" and "-" | / | The Kafka topic were the temperature in fahrenheit can be found | diff --git a/applications/converter/build.gradle.kts b/applications/converter/build.gradle.kts new file mode 100644 index 0000000..3d80c06 --- /dev/null +++ b/applications/converter/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + kotlin("jvm") version "2.0.0" + kotlin("plugin.allopen") version "2.0.0" + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +dependencies { + // common + implementation(project(":common")) + + // quarkus + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-arc") + testImplementation("io.quarkus:quarkus-junit5") + + // libraries + implementation("io.quarkus:quarkus-kafka-client") + implementation("io.quarkus:quarkus-kafka-streams") +} + +group = "fr.faraphel" +version = "1.0-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") +} +allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("jakarta.persistence.Entity") + annotation("io.quarkus.test.junit.QuarkusTest") +} + +tasks.withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_21.toString() + kotlinOptions.javaParameters = true +} \ No newline at end of file diff --git a/applications/converter/gradle.properties b/applications/converter/gradle.properties new file mode 100644 index 0000000..a890bb8 --- /dev/null +++ b/applications/converter/gradle.properties @@ -0,0 +1,6 @@ +#Gradle properties +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.12.0 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.12.0 \ No newline at end of file diff --git a/applications/converter/gradle/wrapper/gradle-wrapper.jar b/applications/converter/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/applications/converter/gradle/wrapper/gradle-wrapper.jar differ diff --git a/applications/converter/gradle/wrapper/gradle-wrapper.properties b/applications/converter/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0d18421 --- /dev/null +++ b/applications/converter/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/applications/converter/gradlew b/applications/converter/gradlew new file mode 100755 index 0000000..fbd7c51 --- /dev/null +++ b/applications/converter/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +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 +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/applications/converter/gradlew.bat b/applications/converter/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/applications/converter/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/applications/converter/settings.gradle.kts b/applications/converter/settings.gradle.kts new file mode 100644 index 0000000..4e76cdd --- /dev/null +++ b/applications/converter/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + val quarkusPluginVersion: String by settings + val quarkusPluginId: String by settings + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id(quarkusPluginId) version quarkusPluginVersion + } +} +rootProject.name = "converter" + +// Dependencies +include(":common") +project(":common").projectDir = file("../common") \ No newline at end of file diff --git a/applications/converter/src/main/docker/Dockerfile.jvm b/applications/converter/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..ed5c8f4 --- /dev/null +++ b/applications/converter/src/main/docker/Dockerfile.jvm @@ -0,0 +1,96 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/converter-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/converter-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/converter-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 build/quarkus-app/*.jar /deployments/ +COPY --chown=185 build/quarkus-app/app/ /deployments/app/ +COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ + +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/applications/converter/src/main/docker/Dockerfile.legacy-jar b/applications/converter/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 0000000..48a29d2 --- /dev/null +++ b/applications/converter/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,92 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.package.jar.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/converter-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/converter-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/converter-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +COPY build/lib/* /deployments/lib/ +COPY build/*-runner.jar /deployments/quarkus-run.jar + +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/applications/converter/src/main/docker/Dockerfile.native b/applications/converter/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..3eb1273 --- /dev/null +++ b/applications/converter/src/main/docker/Dockerfile.native @@ -0,0 +1,26 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/converter . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/converter +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/applications/converter/src/main/docker/Dockerfile.native-micro b/applications/converter/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..f72e5ac --- /dev/null +++ b/applications/converter/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,29 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/converter . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/converter +# +### +FROM quay.io/quarkus/quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/applications/converter/src/main/kotlin/fr/faraphel/converter/Main.kt b/applications/converter/src/main/kotlin/fr/faraphel/converter/Main.kt new file mode 100644 index 0000000..7bc5ce2 --- /dev/null +++ b/applications/converter/src/main/kotlin/fr/faraphel/converter/Main.kt @@ -0,0 +1,41 @@ +package fr.faraphel.converter + +import fr.faraphel.common.error.MissingEnvironmentException +import fr.faraphel.common.temperature.celsius +import fr.faraphel.converter.kafka.Converter +import io.quarkus.runtime.QuarkusApplication +import io.quarkus.runtime.annotations.QuarkusMain +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toJavaDuration + + +@QuarkusMain +class Main : QuarkusApplication { + override fun run(vararg args: String?): Int { + // get the kafka server address + val kafkaServer = System.getenv("KAFKA_BOOTSTRAP_SERVERS") + ?: throw MissingEnvironmentException("KAFKA_BOOTSTRAP_SERVERS") + + // get the topics names + val topicTemperatureCelsius: String = System.getenv("TEMPERATURE_CELSIUS_TOPIC") + ?: throw MissingEnvironmentException("TEMPERATURE_CELSIUS_TOPIC") + val topicTemperatureFahrenheit: String = System.getenv("TEMPERATURE_FAHRENHEIT_TOPIC") + ?: throw MissingEnvironmentException("TEMPERATURE_FAHRENHEIT_TOPIC") + + // create a converter that will convert values coming from the Celsius topic to the Fahrenheit topic + val converter = Converter( + server=kafkaServer, + inputTopic=topicTemperatureCelsius, + outputTopic=topicTemperatureFahrenheit, + ) { temperature -> temperature.celsius.asFahrenheit } + + // start converting values + converter.start() + + // indefinitely wait for the converter to be paused + while (!converter.isPaused) + Thread.sleep(1.seconds.toJavaDuration()) + + return 0 + } +} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Converter.kt b/applications/converter/src/main/kotlin/fr/faraphel/converter/kafka/Converter.kt similarity index 85% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Converter.kt rename to applications/converter/src/main/kotlin/fr/faraphel/converter/kafka/Converter.kt index 6b8bec3..74136cb 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/Converter.kt +++ b/applications/converter/src/main/kotlin/fr/faraphel/converter/kafka/Converter.kt @@ -1,4 +1,4 @@ -package fr.faraphel.m1_pe_kafka.kafka +package fr.faraphel.converter.kafka import org.apache.kafka.common.serialization.Serdes import org.apache.kafka.streams.KafkaStreams @@ -19,7 +19,7 @@ import java.util.* */ class Converter( private val server: String, - private val identifier: String = "converter", + private val identifier: String = "fr/faraphel/converter", private val inputTopic: String, private val outputTopic: String, private val converter: (Double) -> Double, @@ -54,23 +54,30 @@ class Converter( * Start the conversion process * @see KafkaStreams.start */ - fun start() = streams.start() + fun start() = this.streams.start() /** * Pause the conversion process * @see KafkaStreams.pause */ - fun pause() = streams.pause() + fun pause() = this.streams.pause() /** * Resume a paused conversion process * @see KafkaStreams.resume */ - fun resume() = streams.resume() + fun resume() = this.streams.resume() /** * Stop a conversion process * @see KafkaStreams.close */ - fun stop() = streams.close() + fun stop() = this.streams.close() + + /** + * Indicate if the conversion is paused + * @see KafkaStreams.isPaused + */ + val isPaused: Boolean + get() = this.streams.isPaused } diff --git a/applications/converter/src/main/resources/application.properties b/applications/converter/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/applications/producer/.dockerignore b/applications/producer/.dockerignore new file mode 100644 index 0000000..4361d2f --- /dev/null +++ b/applications/producer/.dockerignore @@ -0,0 +1,5 @@ +* +!build/*-runner +!build/*-runner.jar +!build/lib/* +!build/quarkus-app/* \ No newline at end of file diff --git a/applications/producer/.gitignore b/applications/producer/.gitignore new file mode 100644 index 0000000..216783d --- /dev/null +++ b/applications/producer/.gitignore @@ -0,0 +1,39 @@ +# Gradle +.gradle/ +build/ + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/applications/producer/README.md b/applications/producer/README.md new file mode 100644 index 0000000..4ec7f47 --- /dev/null +++ b/applications/producer/README.md @@ -0,0 +1,23 @@ +# Module: Converter + +This module contain the code responsible for getting the current temperature at a given location thanks to the +[Open-Meteo](https://open-meteo.com/) API, and sending it into a topic. + +## Build + +This project is built automatically when using [docker-compose](https://docs.docker.com/compose/) at the root. + +However, you can still build it manually with the following command : +```shell +./gradlew build +``` + +## Configuration + +This module can be easily modified with the following environment variables : + +| Name | Required | Format | Default | Description | +|-------------------------|----------|-----------------------------------------------------|---------|--------------------------------------------------------------| +| KAFKA_BOOTSTRAP_SERVERS | true | \[:port] | / | The Kafka server address | +| TEMPERATURE_TOPIC | true | string of alphanumeric characters, ".", "_" and "-" | / | The Kafka topic were the temperature in celsius can be found | +| TEMPERATURE_LOCATION | true | \, \ | / | The coordinates where to get the temperatures from | diff --git a/applications/producer/build.gradle.kts b/applications/producer/build.gradle.kts new file mode 100644 index 0000000..3be7424 --- /dev/null +++ b/applications/producer/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + kotlin("jvm") version "2.0.0" + kotlin("plugin.allopen") version "2.0.0" + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +dependencies { + // common + implementation(project(":common")) + + // quarkus + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-arc") + testImplementation("io.quarkus:quarkus-junit5") + + // libraries + implementation("io.quarkus:quarkus-kafka-client") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.8.9") +} + +group = "fr.faraphel" +version = "1.0-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") +} +allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("jakarta.persistence.Entity") + annotation("io.quarkus.test.junit.QuarkusTest") +} + +tasks.withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_21.toString() + kotlinOptions.javaParameters = true +} diff --git a/applications/producer/gradle.properties b/applications/producer/gradle.properties new file mode 100644 index 0000000..a890bb8 --- /dev/null +++ b/applications/producer/gradle.properties @@ -0,0 +1,6 @@ +#Gradle properties +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.12.0 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.12.0 \ No newline at end of file diff --git a/applications/producer/gradle/wrapper/gradle-wrapper.jar b/applications/producer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/applications/producer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/applications/producer/gradle/wrapper/gradle-wrapper.properties b/applications/producer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0d18421 --- /dev/null +++ b/applications/producer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/applications/producer/gradlew b/applications/producer/gradlew new file mode 100755 index 0000000..fbd7c51 --- /dev/null +++ b/applications/producer/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +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 +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/applications/producer/gradlew.bat b/applications/producer/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/applications/producer/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/applications/producer/settings.gradle.kts b/applications/producer/settings.gradle.kts new file mode 100644 index 0000000..ec510df --- /dev/null +++ b/applications/producer/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + val quarkusPluginVersion: String by settings + val quarkusPluginId: String by settings + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id(quarkusPluginId) version quarkusPluginVersion + } +} +rootProject.name = "producer" + +// Dependencies +include(":common") +project(":common").projectDir = file("../common") \ No newline at end of file diff --git a/applications/producer/src/main/docker/Dockerfile.jvm b/applications/producer/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..21a8dd0 --- /dev/null +++ b/applications/producer/src/main/docker/Dockerfile.jvm @@ -0,0 +1,96 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/producer-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/producer-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/producer-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 build/quarkus-app/*.jar /deployments/ +COPY --chown=185 build/quarkus-app/app/ /deployments/app/ +COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ + +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/applications/producer/src/main/docker/Dockerfile.legacy-jar b/applications/producer/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 0000000..05e7f0a --- /dev/null +++ b/applications/producer/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,92 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.package.jar.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/producer-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/producer-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/producer-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +COPY build/lib/* /deployments/lib/ +COPY build/*-runner.jar /deployments/quarkus-run.jar + +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/applications/producer/src/main/docker/Dockerfile.native b/applications/producer/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..87366bd --- /dev/null +++ b/applications/producer/src/main/docker/Dockerfile.native @@ -0,0 +1,26 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/producer . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/producer +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/applications/producer/src/main/docker/Dockerfile.native-micro b/applications/producer/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..62a9c2f --- /dev/null +++ b/applications/producer/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,29 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/producer . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/producer +# +### +FROM quay.io/quarkus/quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/applications/producer/src/main/kotlin/fr/faraphel/producer/Main.kt b/applications/producer/src/main/kotlin/fr/faraphel/producer/Main.kt new file mode 100644 index 0000000..cc85006 --- /dev/null +++ b/applications/producer/src/main/kotlin/fr/faraphel/producer/Main.kt @@ -0,0 +1,40 @@ +package fr.faraphel.producer + +import fr.faraphel.common.error.MissingEnvironmentException +import fr.faraphel.producer.kafka.TemperatureProducer +import io.quarkus.runtime.QuarkusApplication +import io.quarkus.runtime.annotations.QuarkusMain + + +@QuarkusMain +class Main : QuarkusApplication { + override fun run(vararg args: String?): Int { + // get the kafka server address + val kafkaServer = System.getenv("KAFKA_BOOTSTRAP_SERVERS") + ?: throw MissingEnvironmentException("KAFKA_BOOTSTRAP_SERVERS") + + // get the topic name + val topicTemperature: String = System.getenv("TEMPERATURE_TOPIC") + ?: throw MissingEnvironmentException("TEMPERATURE_TOPIC") + + // get the location of the temperature to get + val location = System.getenv("TEMPERATURE_LOCATION") + ?: throw MissingEnvironmentException("TEMPERATURE_LOCATION") + + // parse the location to get the latitude and longitude + val (latitude, longitude) = location.split(",").map { coordinate -> coordinate.trim().toDouble() } + + // create a producer that will generate temperature values based on the current temperature. + val producer = TemperatureProducer( + server=kafkaServer, + latitude=latitude, + longitude=longitude, + topic=topicTemperature + ) + + // indefinitely produce new temperatures values + producer.produceForever() + + return 0 + } +} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/TemperatureProducer.kt b/applications/producer/src/main/kotlin/fr/faraphel/producer/kafka/TemperatureProducer.kt similarity index 93% rename from src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/TemperatureProducer.kt rename to applications/producer/src/main/kotlin/fr/faraphel/producer/kafka/TemperatureProducer.kt index 6436119..adb00bc 100644 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/TemperatureProducer.kt +++ b/applications/producer/src/main/kotlin/fr/faraphel/producer/kafka/TemperatureProducer.kt @@ -1,8 +1,8 @@ -package fr.faraphel.m1_pe_kafka.kafka +package fr.faraphel.producer.kafka import com.google.gson.Gson import com.google.gson.JsonObject -import fr.faraphel.m1_pe_kafka.error.http.HttpException +import fr.faraphel.common.error.http.HttpException import org.apache.kafka.clients.producer.KafkaProducer import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.clients.producer.ProducerRecord @@ -29,7 +29,7 @@ class TemperatureProducer( private val latitude: Double, private val longitude: Double, private val topic: String, -) : Thread() { +) { companion object { private val BASE_API_BUILDER: okhttp3.HttpUrl.Builder = okhttp3.HttpUrl.Builder() ///< the Url builder for the API .scheme("https") // use the https protocol @@ -78,7 +78,7 @@ class TemperatureProducer( val response = httpClient.newCall(this.apiRequest).execute() // check for a successful response if (!response.isSuccessful) - // in case of error, raise a http exception + // in case of error, raise a http exception throw HttpException(response.code, response.message) // parse the response into a map of string to string @@ -102,13 +102,7 @@ class TemperatureProducer( // produce a data this.produce() // wait for the cooldown - sleep(frequency) + Thread.sleep(frequency) } } - - /** - * Thread entrypoint - * @see produceForever - */ - override fun run() = this.produceForever() } diff --git a/applications/producer/src/main/resources/application.properties b/applications/producer/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yaml b/docker-compose.yaml index c84c84b..5e147b8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,18 +27,32 @@ services: depends_on: - zookeeper - # Our application - application: + # Our producer + producer: build: - context: . + context: ./applications/producer + dockerfile: ./src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm} + environment: + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + TEMPERATURE_TOPIC: "temperature-celsius" + TEMPERATURE_LOCATION: 49.9, 2.3 + networks: + - kafka + depends_on: + - kafka + + # Our consumer + consumer: + build: + context: ./applications/consumer dockerfile: ./src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm} ports: - "8080:8080" environment: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 - TEMPERATURE_LOCATION: 49.9, 2.3 + TEMPERATURE_TOPIC: "temperature-fahrenheit" healthcheck: - test: curl --fail http://localhost:8080/ping + test: curl --fail http://localhost:8080/v1/ping start_period: 10s timeout: 5s interval: 60s @@ -46,9 +60,24 @@ services: networks: - kafka depends_on: - - zookeeper - kafka + # Our converter + converter: + build: + context: ./applications/converter + dockerfile: ./src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm} + environment: + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + TEMPERATURE_CELSIUS_TOPIC: "temperature-celsius" + TEMPERATURE_FAHRENHEIT_TOPIC: "temperature-fahrenheit" + networks: + - kafka + depends_on: + - kafka + - producer + - consumer + networks: # the Kafka network kafka: diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index f391fe5..0000000 --- a/gradle.properties +++ /dev/null @@ -1,11 +0,0 @@ -# Gradle properties -quarkusPluginId=io.quarkus -quarkusPluginVersion=3.11.1 -quarkusPlatformGroupId=io.quarkus.platform -quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.11.1 - -# Kafka Properties -quarkus.analytics.disabled=true -quarkus.kafka-streams.bootstrap-servers=${KAFKA_BOOTSTRAP_SERVERS:kafka:9092} -kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:kafka:9092} diff --git a/m1-PE-Kafka.iml b/m1-PE-Kafka.iml new file mode 100644 index 0000000..9a5cfce --- /dev/null +++ b/m1-PE-Kafka.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/Main.kt b/src/main/kotlin/fr/faraphel/m1_pe_kafka/Main.kt deleted file mode 100644 index b1f0dfb..0000000 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/Main.kt +++ /dev/null @@ -1,109 +0,0 @@ -package fr.faraphel.m1_pe_kafka - -import fr.faraphel.m1_pe_kafka.error.MissingEnvironmentException -import fr.faraphel.m1_pe_kafka.kafka.AdminUtils -import fr.faraphel.m1_pe_kafka.kafka.Consumer -import fr.faraphel.m1_pe_kafka.kafka.Converter -import fr.faraphel.m1_pe_kafka.kafka.TemperatureProducer -import fr.faraphel.m1_pe_kafka.rest.TemperatureEndpoint -import fr.faraphel.m1_pe_kafka.utils.celsius -import io.quarkus.runtime.Quarkus -import io.quarkus.runtime.QuarkusApplication -import io.quarkus.runtime.annotations.QuarkusMain -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter - - -/** - * The main class. - * Contains the entrypoint of the program. - */ -@QuarkusMain -class Main : QuarkusApplication { - /** - * The entrypoint of the program - * @param args command line arguments - * @return the result code of the program - * @throws MissingEnvironmentException a required environment variable from the configuration is missing - */ - override fun run(vararg args: String?): Int { - // create a time formatter - val timeFormatter = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss.SSS") - .withZone(ZoneId.systemDefault()) - - // get the kafka server address - val kafkaServer = System.getenv("KAFKA_BOOTSTRAP_SERVERS") - ?: throw MissingEnvironmentException("KAFKA_BOOTSTRAP_SERVERS") - - // get the topics name - val topicTemperatureCelsius: String = System.getenv("TOPIC_TEMPERATURE_CELSIUS") - ?: "temperature-celsius" - val topicTemperatureFahrenheit: String = System.getenv("TOPIC_TEMPERATURE_FAHRENHEIT") - ?: "temperature-fahrenheit" - - // create an admin object - val admin = AdminUtils(kafkaServer) - // create our topics - admin.createTopics(topicTemperatureCelsius, topicTemperatureFahrenheit) - - // get the location of the temperature to get - val location = System.getenv("TEMPERATURE_LOCATION") - ?: throw MissingEnvironmentException("TEMPERATURE_LOCATION") - - // parse the location to get the latitude and longitude - val (latitude, longitude) = location.split(",").map { coordinate -> coordinate.trim().toDouble() } - - // create a producer that will generate temperature values based on the current temperature at Amiens (France). - val producer = TemperatureProducer( - server=kafkaServer, - latitude=latitude, - longitude=longitude, - topic=topicTemperatureCelsius - ) - - // create a converter that will convert values coming from the Celsius topic to the Fahrenheit topic - val converter = Converter( - server=kafkaServer, - inputTopic=topicTemperatureCelsius, - outputTopic=topicTemperatureFahrenheit - ) { temperature -> temperature.celsius.asFahrenheit } - - // create a consumer that will print the received values in the Fahrenheit topic - val consumer = Consumer( - server=kafkaServer, - topic=topicTemperatureFahrenheit - ) { message -> - // format the time of the message to a proper string - val time = timeFormatter.format(Instant.ofEpochMilli(message.timestamp())) - // print the value - println("[${time}] ${message.value()}°F") - // update the value for the API - TemperatureEndpoint.setTemperature(message.value()) - } - - // run all the clients - producer.start() - consumer.start() - converter.start() - - // wait for them to finish before closing - producer.join() - consumer.join() - - // close the converter if the others clients are done - converter.stop() - - return 0 - } -} - -/** - * The main function. - * Simply call the entrypoint. - * Used to run the program directly with Kotlin. - */ -fun main(args: Array) { - Quarkus.run(Main::class.java, *args) -} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/AdminUtils.kt b/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/AdminUtils.kt deleted file mode 100644 index 748013e..0000000 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/kafka/AdminUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package fr.faraphel.m1_pe_kafka.kafka - -import org.apache.kafka.clients.admin.Admin -import org.apache.kafka.clients.admin.AdminClientConfig -import org.apache.kafka.clients.admin.CreateTopicsResult -import org.apache.kafka.clients.admin.NewTopic -import java.util.* - - -/** - * A wrapper around KafkaAdminClient to simplify its configuration. - * @param server the kafka server address - * @param identifier the kafka identifier for the configuration - * @see Admin - */ -class AdminUtils( - private val server: String, - private val identifier: String = "admin" -) { - private val adminConfig = Properties().apply { - // set the identifier - this[AdminClientConfig.CLIENT_ID_CONFIG] = identifier - // set the server information - this[AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG] = server - } - - private val admin = Admin.create(adminConfig) - - /** - * Create the topics in the kafka server. - * @param topics the names of the topics to create. - * @return the result of the operation. - * @see Admin.createTopics - */ - fun createTopics(vararg topics: String): CreateTopicsResult { - // convert the topics name into the corresponding operation - val operations = topics.map { topic -> NewTopic(topic, 1, 1) } - // run the command - return this.admin.createTopics(operations) - } -} diff --git a/src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/TemperatureEndpoint.kt b/src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/TemperatureEndpoint.kt deleted file mode 100644 index 7472ba1..0000000 --- a/src/main/kotlin/fr/faraphel/m1_pe_kafka/rest/TemperatureEndpoint.kt +++ /dev/null @@ -1,32 +0,0 @@ -package fr.faraphel.m1_pe_kafka.rest - -import jakarta.ws.rs.GET -import jakarta.ws.rs.Path - - -/** - * This API endpoint return the latest temperature measured (in Fahrenheit) - */ -@Path("temperature") -class TemperatureEndpoint { - companion object { - private var temperature: Double? = null ///< the latest temperature value - - /** - * Setter to update the latest temperature value - * @param temperature the new temperature value - */ - fun setTemperature(temperature: Double) { - this.temperature = temperature - } - } - - /** - * Handler for a GET request on this endpoint - * @return the latest temperature measured - */ - @GET - fun get(): Double? { - return temperature - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 9817ace..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -quarkus.kafka-streams.application-id=fr.faraphel.m1_pe_kafka