diff --git a/modules/hivemq-edge-module-redis/.idea/.gitignore b/modules/hivemq-edge-module-redis/.idea/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/modules/hivemq-edge-module-redis/.idea/.name b/modules/hivemq-edge-module-redis/.idea/.name new file mode 100644 index 0000000000..0316ac6373 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/.name @@ -0,0 +1 @@ +hivemq-postgresql-protocol-adapter \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/compiler.xml b/modules/hivemq-edge-module-redis/.idea/compiler.xml new file mode 100644 index 0000000000..fb7f4a8a46 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/gradle.xml b/modules/hivemq-edge-module-redis/.idea/gradle.xml new file mode 100644 index 0000000000..5cbb93ace3 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/jarRepositories.xml b/modules/hivemq-edge-module-redis/.idea/jarRepositories.xml new file mode 100644 index 0000000000..a529ef2a03 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/misc.xml b/modules/hivemq-edge-module-redis/.idea/misc.xml new file mode 100644 index 0000000000..25d34a4744 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/modules.xml b/modules/hivemq-edge-module-redis/.idea/modules.xml new file mode 100644 index 0000000000..94281d65a9 --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/.idea/vcs.xml b/modules/hivemq-edge-module-redis/.idea/vcs.xml new file mode 100644 index 0000000000..d483ca8fed --- /dev/null +++ b/modules/hivemq-edge-module-redis/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/HEADER b/modules/hivemq-edge-module-redis/HEADER new file mode 100644 index 0000000000..6e731e9277 --- /dev/null +++ b/modules/hivemq-edge-module-redis/HEADER @@ -0,0 +1,13 @@ +Copyright 2023-present HiveMQ GmbH + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/build.gradle.kts b/modules/hivemq-edge-module-redis/build.gradle.kts new file mode 100644 index 0000000000..fed97b03b3 --- /dev/null +++ b/modules/hivemq-edge-module-redis/build.gradle.kts @@ -0,0 +1,162 @@ +import nl.javadude.gradle.plugins.license.DownloadLicensesExtension.license + +plugins { + java + id("com.github.sgtsilvio.gradle.utf8") + id("com.github.johnrengelman.shadow") + id("com.github.hierynomus.license") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +group = "com.hivemq" +version = "2025.8 ALPHA" + +repositories { + mavenCentral() + maven { url = uri("https://jitpack.io") } + exclusiveContent { + forRepository { + maven { + url = uri("https://jitpack.io") + } + } + filter { + includeGroup("com.github.simon622.mqtt-sn") + includeGroup("com.github.simon622") + } + } +} + + +dependencies { + compileOnly("com.hivemq:hivemq-edge-adapter-sdk:${property("hivemq-edge-adapter-sdk.version")}") + compileOnly("commons-io:commons-io:${property("commons-io.version")}") + compileOnly("com.fasterxml.jackson.core:jackson-databind:${property("jackson.version")}") + compileOnly("org.slf4j:slf4j-api:${property("slf4j.version")}") + implementation("redis.clients:jedis:6.0.0") +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api:${property("junit.jupiter.version")}") + testImplementation("org.junit.jupiter:junit-jupiter-params:${property("junit.jupiter.version")}") + testImplementation("org.junit.platform:junit-platform-launcher:${property("junit.jupiter.platform.version")}") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${property("junit.jupiter.version")}") + testImplementation("com.hivemq:hivemq-edge-adapter-sdk:${property("hivemq-edge-adapter-sdk.version")}") + testImplementation("org.mockito:mockito-core:${property("mockito.version")}") + testImplementation("com.fasterxml.jackson.core:jackson-databind:${property("jackson.version")}") +} + +tasks.test { + useJUnitPlatform() +} + +license { + header = file("HEADER") + mapping("java", "SLASHSTAR_STYLE") +} + +downloadLicenses { + aliases = mapOf( + license("Apache License, Version 2.0", "https://opensource.org/licenses/Apache-2.0") to listOf( + "Apache 2", + "Apache 2.0", + "Apache-2.0", + "Apache License 2.0", + "Apache License, 2.0", + "Apache License v2.0", + "Apache License, Version 2", + "Apache License Version 2.0", + "Apache License, Version 2.0", + "Apache License, version 2.0", + "The Apache License, Version 2.0", + "Apache Software License - Version 2.0", + "Apache Software License, version 2.0", + "The Apache Software License, Version 2.0" + ), + license("MIT License", "https://opensource.org/licenses/MIT") to listOf( + "MIT License", + "MIT license", + "The MIT License", + "The MIT License (MIT)" + ), + license("CDDL, Version 1.0", "https://opensource.org/licenses/CDDL-1.0") to listOf( + "CDDL, Version 1.0", + "Common Development and Distribution License 1.0", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", + license("CDDL", "https://glassfish.dev.java.net/public/CDDLv1.0.html") + ), + license("CDDL, Version 1.1", "https://oss.oracle.com/licenses/CDDL+GPL-1.1") to listOf( + "CDDL 1.1", + "CDDL, Version 1.1", + "Common Development And Distribution License 1.1", + "CDDL+GPL License", + "CDDL + GPLv2 with classpath exception", + "Dual license consisting of the CDDL v1.1 and GPL v2", + "CDDL or GPLv2 with exceptions", + "CDDL/GPLv2+CE" + ), + license("LGPL, Version 2.0", "https://opensource.org/licenses/LGPL-2.0") to listOf( + "LGPL, Version 2.0", + "GNU General Public License, version 2" + ), + license("LGPL, Version 2.1", "https://opensource.org/licenses/LGPL-2.1") to listOf( + "LGPL, Version 2.1", + "LGPL, version 2.1", + "GNU Lesser General Public License version 2.1 (LGPLv2.1)", + license("GNU Lesser General Public License", "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html") + ), + license("LGPL, Version 3.0", "https://opensource.org/licenses/LGPL-3.0") to listOf( + "LGPL, Version 3.0", + "Lesser General Public License, version 3 or greater" + ), + license("EPL, Version 1.0", "https://opensource.org/licenses/EPL-1.0") to listOf( + "EPL, Version 1.0", + "Eclipse Public License - v 1.0", + "Eclipse Public License - Version 1.0", + license("Eclipse Public License", "http://www.eclipse.org/legal/epl-v10.html") + ), + license("EPL, Version 2.0", "https://opensource.org/licenses/EPL-2.0") to listOf( + "EPL 2.0", + "EPL, Version 2.0" + ), + license("EDL, Version 1.0", "https://www.eclipse.org/org/documents/edl-v10.php") to listOf( + "EDL 1.0", + "EDL, Version 1.0", + "Eclipse Distribution License - v 1.0" + ), + license("BSD 3-Clause License", "https://opensource.org/licenses/BSD-3-Clause") to listOf( + "BSD 3-clause", + "BSD-3-Clause", + "BSD 3-Clause License", + "3-Clause BSD License", + "New BSD License", + license("BSD", "http://asm.ow2.org/license.html"), + license("BSD", "http://asm.objectweb.org/license.html"), + license("BSD", "LICENSE.txt") + ), + license("Bouncy Castle License", "https://www.bouncycastle.org/licence.html") to listOf( + "Bouncy Castle Licence" + ), + license("W3C License", "https://opensource.org/licenses/W3C") to listOf( + "W3C License", + "W3C Software Copyright Notice and License", + "The W3C Software License" + ), + license("CC0", "https://creativecommons.org/publicdomain/zero/1.0/") to listOf( + "CC0", + "Public Domain" + ) + ) + + dependencyConfiguration = "runtimeClasspath" +} + +val javaComponent = components["java"] as AdhocComponentWithVariants +javaComponent.withVariantsFromConfiguration(configurations.shadowRuntimeElements.get()) { + skip() +} diff --git a/modules/hivemq-edge-module-redis/gradle.properties b/modules/hivemq-edge-module-redis/gradle.properties new file mode 100644 index 0000000000..ce8cdff1d0 --- /dev/null +++ b/modules/hivemq-edge-module-redis/gradle.properties @@ -0,0 +1,24 @@ +# +# hivemq dependencies +# +hivemq-edge-adapter-sdk.version=2025.8 +# +# main dependencies +# +commons-io.version=2.13.0 +jackson.version=2.17.0 +slf4j.version=1.7.30 +slf4jfull.version=2.0.16 +# +# plugins +# +plugin.utf8.version=0.1.0 +plugin.shadow.version=7.1.2 +plugin.license.version=0.16.1 +plugin.dependencycheck.version=7.4.4 +# +# tests +# +junit.jupiter.version=5.7.1 +junit.jupiter.platform.version=1.7.1 +mockito.version=5.7.0 diff --git a/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.jar b/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..249e5832f0 Binary files /dev/null and b/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.jar differ diff --git a/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.properties b/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..91e0fa781f --- /dev/null +++ b/modules/hivemq-edge-module-redis/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 24 08:26:06 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/modules/hivemq-edge-module-redis/gradlew b/modules/hivemq-edge-module-redis/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/modules/hivemq-edge-module-redis/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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 "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/modules/hivemq-edge-module-redis/gradlew.bat b/modules/hivemq-edge-module-redis/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/modules/hivemq-edge-module-redis/gradlew.bat @@ -0,0 +1,89 @@ +@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 execute + +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 execute + +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 + +: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 %* + +: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/modules/hivemq-edge-module-redis/settings.gradle.kts b/modules/hivemq-edge-module-redis/settings.gradle.kts new file mode 100644 index 0000000000..0a4d4814d0 --- /dev/null +++ b/modules/hivemq-edge-module-redis/settings.gradle.kts @@ -0,0 +1,10 @@ +rootProject.name = "hivemq-redis-protocol-adapter" + +pluginManagement { + plugins { + id("com.github.johnrengelman.shadow") version "${extra["plugin.shadow.version"]}" + id("com.github.sgtsilvio.gradle.utf8") version "${extra["plugin.utf8.version"]}" + id("com.github.hierynomus.license") version "${extra["plugin.license.version"]}" + id("org.owasp.dependencycheck") version "${extra["plugin.dependencycheck.version"]}" + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapter.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapter.java new file mode 100644 index 0000000000..221f7d987a --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapter.java @@ -0,0 +1,187 @@ +/* + * Copyright 2024-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.hivemq.adapter.sdk.api.ProtocolAdapterInformation; +import com.hivemq.adapter.sdk.api.factories.AdapterFactories; +import com.hivemq.adapter.sdk.api.factories.DataPointFactory; +import com.hivemq.adapter.sdk.api.model.*; +import com.hivemq.adapter.sdk.api.polling.batch.BatchPollingInput; +import com.hivemq.adapter.sdk.api.polling.batch.BatchPollingOutput; +import com.hivemq.adapter.sdk.api.polling.batch.BatchPollingProtocolAdapter; +import com.hivemq.adapter.sdk.api.state.ProtocolAdapterState; +import com.hivemq.adapter.sdk.api.tag.Tag; +import com.hivemq.edge.adapters.redis.config.RedisAdapterConfig; +import com.hivemq.edge.adapters.redis.config.RedisAdapterTag; +import com.hivemq.edge.adapters.redis.config.RedisAdapterTagDefinition; +import com.hivemq.edge.adapters.redis.helpers.RedisHelpers; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +import java.util.List; +import java.util.Objects; + +import static com.hivemq.adapter.sdk.api.state.ProtocolAdapterState.ConnectionStatus.STATELESS; + + +public class RedisPollingProtocolAdapter implements BatchPollingProtocolAdapter { + private static final @NotNull Logger log = LoggerFactory.getLogger(RedisPollingProtocolAdapter.class); + private static final @NotNull ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final @NotNull RedisAdapterConfig adapterConfig; + private final @NotNull ProtocolAdapterInformation adapterInformation; + private final @NotNull ProtocolAdapterState protocolAdapterState; + private final @NotNull RedisHelpers redisHelpers; + private final @NotNull List tags; + private JedisPool jedisPool; + private final @NotNull String adapterId; + private final @NotNull AdapterFactories adapterFactories; + + public RedisPollingProtocolAdapter(final @NotNull ProtocolAdapterInformation adapterInformation, + final @NotNull ProtocolAdapterInput input) { + this.tags = input.getTags(); + this.redisHelpers = new RedisHelpers(); + this.adapterId = input.getAdapterId(); + this.adapterInformation = adapterInformation; + this.adapterConfig = input.getConfig(); + this.protocolAdapterState = input.getProtocolAdapterState(); + this.adapterFactories = input.adapterFactories(); + } + + @Override + public @NotNull String getId() { + return adapterId; + } + + @Override + public void start(final @NotNull ProtocolAdapterStartInput input, final @NotNull ProtocolAdapterStartOutput output) { + /* Test connection to the database when starting the adapter. */ + try { + // Start Initialization + jedisPool = redisHelpers.initJedisPool(adapterConfig); + + if(!jedisPool.isClosed()){ + output.startedSuccessfully(); + protocolAdapterState.setConnectionStatus(ProtocolAdapterState.ConnectionStatus.CONNECTED); + } else { + output.failStart(new Throwable("Error connecting Redis server, please check the configuration"), + "Error connecting Redis server, please check the configuration"); + protocolAdapterState.setConnectionStatus(ProtocolAdapterState.ConnectionStatus.DISCONNECTED); + } + } catch (final Exception e) { + output.failStart(e, null); + protocolAdapterState.setConnectionStatus(ProtocolAdapterState.ConnectionStatus.DISCONNECTED); + } + } + + @Override + public void stop(final @NotNull ProtocolAdapterStopInput protocolAdapterStopInput, final @NotNull ProtocolAdapterStopOutput protocolAdapterStopOutput) { + jedisPool.close(); + protocolAdapterState.setConnectionStatus(ProtocolAdapterState.ConnectionStatus.DISCONNECTED); + protocolAdapterStopOutput.stoppedSuccessfully(); + } + + + @Override + public @NotNull ProtocolAdapterInformation getProtocolAdapterInformation() { + return adapterInformation; + } + + @Override + public void poll(final @NotNull BatchPollingInput pollingInput, final @NotNull BatchPollingOutput pollingOutput) { + log.debug("Handling tags for Redis protocol adapter"); + tags.forEach(tag -> loadRedisData(pollingOutput, (RedisAdapterTag) tag)); + log.debug("Finished getting tags"); + protocolAdapterState.setConnectionStatus(STATELESS); + pollingOutput.finish(); + log.debug("Finished polling"); + } + + private void loadRedisData(final @NotNull BatchPollingOutput output, final @NotNull RedisAdapterTag tag) { + /* Check if the pool is closed, if not, create a new one */ + if(jedisPool.isClosed()){ + jedisPool = redisHelpers.initJedisPool(adapterConfig); + } + + final RedisAdapterTagDefinition definition = tag.getDefinition(); + + log.debug("Tag : {}", tag.getName()); + + /* Connect to Redis and get the key */ + try { + /* Switch method based on a type of key to retrieve*/ + final ObjectNode node = OBJECT_MAPPER.createObjectNode(); + final DataPointFactory dataPointFactory = adapterFactories.dataPointFactory(); + switch (Objects.requireNonNull(Objects.requireNonNull(definition.getType()).toString())) { + case "HASH": + log.debug("Handling Hash"); + log.debug("Hash Key : {}", definition.getKey()); + log.debug("Field : {}", definition.getField()); + try (final Jedis jedis = jedisPool.getResource();){ + final String result; + if (definition.getAll()){ + result = jedis.hgetAll(definition.getKey()).toString(); + } else { + result = jedis.hget(definition.getKey(), definition.getField()); + } + log.debug("Hash Result : {}",result); + node.put("value", result); + output.addDataPoint(dataPointFactory.create(tag.getName(), node)); + } + break; + case "LIST": + log.debug("Handling List like queue"); + log.debug("List Key : {}", definition.getKey()); + try (final Jedis jedis = jedisPool.getResource();){ + final String result = jedis.rpop(definition.getKey()); + log.debug("List Result : {}",result); + node.put("value", result); + output.addDataPoint(dataPointFactory.create(tag.getName(), node)); + } + break; + default: + log.debug("Handling Default (String)"); + log.debug("String Key : {}", definition.getKey()); + try (final Jedis jedis = jedisPool.getResource();){ + final String result = jedis.get(definition.getKey()); + node.put("value", result); + log.debug(node.toString()); + log.debug("Adding datapoint"); + log.debug(tag.getName()); + output.addDataPoint(dataPointFactory.create(tag.getName(), node)); + } + break; + } + } catch (final Exception e) { + output.fail(e, e.getMessage()); + } + } + + @Override + public int getPollingIntervalMillis() { + return adapterConfig.getPollingIntervalMillis(); + } + + @Override + public int getMaxPollingErrorsBeforeRemoval() { + return adapterConfig.getMaxPollingErrorsBeforeRemoval(); + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterFactory.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterFactory.java new file mode 100644 index 0000000000..6a9077dc31 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + +import com.hivemq.adapter.sdk.api.ProtocolAdapter; +import com.hivemq.adapter.sdk.api.ProtocolAdapterInformation; +import com.hivemq.adapter.sdk.api.factories.ProtocolAdapterFactory; +import com.hivemq.adapter.sdk.api.model.ProtocolAdapterInput; +import com.hivemq.edge.adapters.redis.config.RedisAdapterConfig; +import org.jetbrains.annotations.NotNull; + +public class RedisProtocolAdapterFactory implements ProtocolAdapterFactory { + + @Override + public @NotNull ProtocolAdapterInformation getInformation() { + return RedisProtocolAdapterInformation.INSTANCE; + } + + @Override + public @NotNull ProtocolAdapter createAdapter(final @NotNull ProtocolAdapterInformation adapterInformation, + @NotNull final ProtocolAdapterInput input) { + return new RedisPollingProtocolAdapter(adapterInformation, input); + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformation.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformation.java new file mode 100644 index 0000000000..42a5c4d1fa --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformation.java @@ -0,0 +1,162 @@ +/* + * Copyright 2023-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + + +import com.hivemq.adapter.sdk.api.ProtocolAdapterCapability; +import com.hivemq.adapter.sdk.api.ProtocolAdapterCategory; +import com.hivemq.adapter.sdk.api.ProtocolAdapterInformation; +import com.hivemq.adapter.sdk.api.ProtocolAdapterTag; +import com.hivemq.adapter.sdk.api.config.ProtocolSpecificAdapterConfig; +import com.hivemq.adapter.sdk.api.tag.Tag; +import com.hivemq.edge.adapters.redis.config.RedisAdapterConfig; +import com.hivemq.edge.adapters.redis.config.RedisAdapterTag; +import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; +import java.util.List; + +public class RedisProtocolAdapterInformation implements ProtocolAdapterInformation { + + public static final @NotNull ProtocolAdapterInformation INSTANCE = new RedisProtocolAdapterInformation(); + private static final @NotNull Logger LOG = LoggerFactory.getLogger(RedisProtocolAdapterInformation.class); + + protected RedisProtocolAdapterInformation() { + } + + @Override + public @NotNull String getProtocolName() { + // the returned string will be used for logging information on the protocol adapter + return "Redis"; + } + + @Override + public @NotNull String getProtocolId() { + // this id is very important as this is how the adapters configurations in the config.xml are linked to the adapter implementations. + // any change here means you will need to edit the config.xml + return "redis"; + } + + @Override + public @NotNull List getLegacyProtocolIds() { + return ProtocolAdapterInformation.super.getLegacyProtocolIds(); + } + + @Override + public @NotNull String getDisplayName() { + // the name for this protocol adapter type that will be displayed within edge's ui + return "Redis Protocol Adapter"; + } + + @Override + public @NotNull String getDescription() { + // the description that will be shown for this protocol adapter within edge's ui + return "This protocol adapter allow you to get values from Redis."; + } + + @Override + public @NotNull String getUrl() { + // this url will be displayed in the ui as a link to further documentation on this protocol adapter. + // e.g. this could be a link to the source code and a readme + return "TBD"; + } + + @Override + public @NotNull String getVersion() { + // the version of this protocol adapter, the usage of semantic versioning is advised. + return "1.0.0"; + } + + @Override + public @NotNull EnumSet getCapabilities() { + // this indicates what capabilities this protocol adapter has. E.g. READ/WRITE. See the ProtocolAdapterCapability enum for more information. + return EnumSet.of(ProtocolAdapterCapability.READ); + } + + @Override + public @NotNull String getLogoUrl() { + // this is a default image that is always available. + return "/images/redis.png"; + } + + @Override + public @NotNull String getAuthor() { + // your name/nick + return "HiveMQ"; + } + + @Override + public @Nullable ProtocolAdapterCategory getCategory() { + // this indicates for which use cases this protocol adapter is intended. See the ProtocolAdapterConstants.CATEGORY enum for more information. + return ProtocolAdapterCategory.CONNECTIVITY; + } + + @Override + public List getTags() { + // here you can set which Tags should be applied to this protocol adapter + return List.of(ProtocolAdapterTag.INTERNET, + ProtocolAdapterTag.TCP, + ProtocolAdapterTag.WEB); + } + + + + @Override + public @Nullable String getUiSchema() { + try (final InputStream is = this.getClass() + .getClassLoader() + .getResourceAsStream("redis-adapter-ui-schema.json")) { + if (is == null) { + LOG.warn("The UISchema for the Redis Adapter could not be loaded from resources: Not found."); + return null; + } + return IOUtils.toString(is, StandardCharsets.UTF_8); + } catch (Exception e) { + LOG.warn("The UISchema for the Redis Adapter could not be loaded from resources:", e); + return null; + } + } + + @Override + public int getCurrentConfigVersion() { + return 0; + } + + @Override + public @NotNull Class tagConfigurationClass() { + return RedisAdapterTag.class; + } + + @Override + public @NotNull Class configurationClassNorthbound() { + return RedisAdapterConfig.class; + } + + @Override + public @NotNull Class configurationClassNorthAndSouthbound() { + return RedisAdapterConfig.class; + } + + + +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterConfig.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterConfig.java new file mode 100644 index 0000000000..3743e5f8a3 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterConfig.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis.config; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.hivemq.adapter.sdk.api.annotations.ModuleConfigField; +import com.hivemq.adapter.sdk.api.config.ProtocolSpecificAdapterConfig; +import org.jetbrains.annotations.NotNull; + + +@SuppressWarnings({"unused", "FieldCanBeLocal", "FieldMayBeFinal"}) +@JsonPropertyOrder({ + "url", + "destination"}) +public class RedisAdapterConfig implements ProtocolSpecificAdapterConfig { + + private static final @NotNull String ID_REGEX = "^([a-zA-Z_0-9-_])*$"; + + @JsonProperty(value = "id", required = true) + @ModuleConfigField(title = "Identifier", + description = "Unique identifier for this protocol adapter", + format = ModuleConfigField.FieldType.IDENTIFIER, + required = true, + stringPattern = ID_REGEX, + stringMinLength = 1, + stringMaxLength = 1024) + protected @NotNull String id; + + @JsonProperty(value = "server", required = true) + @ModuleConfigField(title = "Server", + description = "Server address", + format = ModuleConfigField.FieldType.UNSPECIFIED, + required = true, + stringMinLength = 1, + stringMaxLength = 1024) + protected @NotNull String server; + + @JsonProperty(value = "port", required = true) + @ModuleConfigField(title = "Port", + description = "Server port (usually 6379)", + format = ModuleConfigField.FieldType.UNSPECIFIED, + required = true, + stringPattern = ID_REGEX, + stringMinLength = 1, + stringMaxLength = 6) + protected @NotNull Integer port; + + @JsonProperty(value = "username", required = false) + @ModuleConfigField(title = "Username", + description = "Username for the connection to the database (can be empty if only password is required)", + format = ModuleConfigField.FieldType.UNSPECIFIED, + required = false, + stringPattern = ID_REGEX, + stringMinLength = 0, + stringMaxLength = 1024) + protected String username; + + @JsonProperty(value = "password", required = false) + @ModuleConfigField(title = "Password", + description = "Password for the connection to the database", + format = ModuleConfigField.FieldType.UNSPECIFIED, + required = false, + stringPattern = ID_REGEX, + stringMinLength = 0, + stringMaxLength = 1024) + protected String password; + + @JsonProperty("pollingIntervalMillis") + @ModuleConfigField(title = "Polling Interval [ms]", + description = "Time in millisecond that this endpoint will be polled", + numberMin = 100, + required = true, + defaultValue = "1000") + private int pollingIntervalMillis = 1000; + + @JsonProperty("maxPollingErrorsBeforeRemoval") + @ModuleConfigField(title = "Max. Polling Errors", + description = "Max. errors polling the endpoint before the polling daemon is stopped", + numberMin = 3, + defaultValue = "10") + private int maxPollingErrorsBeforeRemoval = 10; + + + public RedisAdapterConfig() { + id = ""; + server = ""; + port = 6379; + username = ""; + password = ""; + } + + public @NotNull String getServer() {return server;} + + public @NotNull Integer getPort() {return port;} + + public String getPassword() {return password;} + + public String getUsername() {return username;} + + public int getPollingIntervalMillis() { + return pollingIntervalMillis; + } + + public int getMaxPollingErrorsBeforeRemoval() { + return maxPollingErrorsBeforeRemoval; + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTag.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTag.java new file mode 100644 index 0000000000..953d40cede --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTag.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.hivemq.adapter.sdk.api.annotations.ModuleConfigField; +import com.hivemq.adapter.sdk.api.tag.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class RedisAdapterTag implements Tag { + + @JsonProperty(value = "name", required = true) + @ModuleConfigField(title = "Name", + description = "name of the tag to be used in mappings", + format = ModuleConfigField.FieldType.MQTT_TAG, + required = true) + private final @NotNull String name; + + @JsonProperty(value = "description") + @ModuleConfigField(title = "Description", + description = "A human readable description of the tag") + private final @NotNull String description; + + @JsonProperty(value = "definition", required = true) + @ModuleConfigField(title = "Definition", + description = "The actual definition of the tag on the device") + private final @NotNull RedisAdapterTagDefinition definition; + + public RedisAdapterTag( + @JsonProperty(value = "name", required = true) final @NotNull String name, + @JsonProperty(value = "description") final @Nullable String description, + @JsonProperty(value = "definition", required = true) final @NotNull RedisAdapterTagDefinition definition) { + this.name = name; + this.description = Objects.requireNonNullElse(description, "no description present."); + this.definition = definition; + } + + @Override + public @NotNull RedisAdapterTagDefinition getDefinition() { + return definition; + } + + @Override + public @NotNull String getName() { + return name; + } + + @Override + public @NotNull String getDescription() { + return description; + } + + @Override + public @NotNull String toString() { + return "RedisTag{" + + "Name='" + + name + + '\'' + + ", Description='" + + description + + '\'' + + ", Definition=" + + definition + + '}'; + } + + @Override + public boolean equals(final @Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final RedisAdapterTag redisAdapterTag = (RedisAdapterTag) o; + return Objects.equals(name, redisAdapterTag.name) && + Objects.equals(description, redisAdapterTag.description) && + Objects.equals(definition, redisAdapterTag.definition); + } + + @Override + public int hashCode() { + return Objects.hash(name, description, definition); + } +} + + diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTagDefinition.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTagDefinition.java new file mode 100644 index 0000000000..c5f24461a3 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisAdapterTagDefinition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.hivemq.adapter.sdk.api.annotations.ModuleConfigField; +import com.hivemq.adapter.sdk.api.tag.TagDefinition; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.Nullable; + +public class RedisAdapterTagDefinition implements TagDefinition { + @JsonProperty(value = "key", required = true) + @ModuleConfigField(title = "Key", + description = "Key to get from Redis", + required = true, + format = ModuleConfigField.FieldType.UNSPECIFIED) + protected @Nullable String key; + + @JsonProperty(value = "field", required = false) + @ModuleConfigField(title = "Field", + description = "Optional field to get from Redis", + defaultValue = "", + format = ModuleConfigField.FieldType.UNSPECIFIED) + protected @Nullable String field; + + @JsonProperty(value = "type", required = true) + @ModuleConfigField(title = "Type", + description = "Type of the Key to get from Redis", + required = true, + defaultValue = "STRING", + format = ModuleConfigField.FieldType.UNSPECIFIED) + protected @Nullable RedisDataType type; + + @JsonProperty(value = "getall", required = true) + @ModuleConfigField(title = "Get all values", + description = "Get all value from the hash", defaultValue = "false", + format = ModuleConfigField.FieldType.BOOLEAN) + protected boolean getAll; + + @JsonCreator + public RedisAdapterTagDefinition( + @JsonProperty("key") @Nullable final String key, + @JsonProperty("field") @Nullable final String field, + @JsonProperty("getall") final boolean getAll, + @JsonProperty("type") @Nullable final RedisDataType type) { + this.key = key; + this.field = field; + this.type = type; + this.getAll = getAll; + + } + + public @Nullable String getKey(){return key;} + public @Nullable String getField(){return field;} + public @Nullable RedisDataType getType(){return type;} + public Boolean getAll(){return getAll;} +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisDataType.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisDataType.java new file mode 100644 index 0000000000..79e80f6392 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/config/RedisDataType.java @@ -0,0 +1,37 @@ +package com.hivemq.edge.adapters.redis.config; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public enum RedisDataType { + STRING("STRING"), + LIST("LIST"), + HASH("HASH"); + + private static final Map BY_LABEL; + + static { + final Map temp = new HashMap<>(); + for (final RedisDataType e : values()) { + temp.put(e.label, e); + } + BY_LABEL = Collections.unmodifiableMap(temp); + } + + public final String label; + + RedisDataType(final String label) { + this.label = label; + } + + + public static RedisDataType valueOfLabel(final String label) { + return BY_LABEL.get(label); + } + + @Override + public String toString() { + return this.label; + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/helpers/RedisHelpers.java b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/helpers/RedisHelpers.java new file mode 100644 index 0000000000..e11aac4e03 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/java/com/hivemq/edge/adapters/redis/helpers/RedisHelpers.java @@ -0,0 +1,24 @@ +package com.hivemq.edge.adapters.redis.helpers; + +import com.hivemq.edge.adapters.redis.config.RedisAdapterConfig; +import org.jetbrains.annotations.NotNull; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +public class RedisHelpers { + // Initialize Jedis Pool + public JedisPool initJedisPool(final @NotNull RedisAdapterConfig adapterConfig) { + final JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + final JedisPool jedisPool; + if(!adapterConfig.getPassword().isEmpty()) { + if (!adapterConfig.getUsername().isEmpty()){ + jedisPool = new JedisPool(jedisPoolConfig,adapterConfig.getServer(),adapterConfig.getPort(),180, adapterConfig.getUsername(),adapterConfig.getPassword()); + } else { + jedisPool = new JedisPool(jedisPoolConfig,adapterConfig.getServer(),adapterConfig.getPort(),180,adapterConfig.getPassword()); + } + } else { + jedisPool = new JedisPool(jedisPoolConfig, adapterConfig.getServer(), adapterConfig.getPort(), 180); + } + return jedisPool; + } +} diff --git a/modules/hivemq-edge-module-redis/src/main/resources/META-INF/services/com.hivemq.adapter.sdk.api.factories.ProtocolAdapterFactory b/modules/hivemq-edge-module-redis/src/main/resources/META-INF/services/com.hivemq.adapter.sdk.api.factories.ProtocolAdapterFactory new file mode 100644 index 0000000000..a2540a9d90 --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/resources/META-INF/services/com.hivemq.adapter.sdk.api.factories.ProtocolAdapterFactory @@ -0,0 +1 @@ +com.hivemq.edge.adapters.redis.RedisProtocolAdapterFactory diff --git a/modules/hivemq-edge-module-redis/src/main/resources/httpd/images/redis.png b/modules/hivemq-edge-module-redis/src/main/resources/httpd/images/redis.png new file mode 100644 index 0000000000..8b8a44172c Binary files /dev/null and b/modules/hivemq-edge-module-redis/src/main/resources/httpd/images/redis.png differ diff --git a/modules/hivemq-edge-module-redis/src/main/resources/redis-adapter-ui-schema.json b/modules/hivemq-edge-module-redis/src/main/resources/redis-adapter-ui-schema.json new file mode 100644 index 0000000000..394180dfdd --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/main/resources/redis-adapter-ui-schema.json @@ -0,0 +1,47 @@ +{ + "ui:tabs": [ + { + "id": "coreFields", + "title": "Settings", + "properties": [ + "id", + "server", + "port", + "username", + "password" + ] + }, + { + "id": "publishing", + "title": "Publishing", + "properties": [ + "maxPollingErrorsBeforeRemoval", + "pollingIntervalMillis" + ] + } + ], + "password": { + "ui:widget": "password" + }, + "type": { + "ui:widget": "select" + }, + "ui:order": [ + "id", + "server", + "port", + "*" + ], + "definition" : { + "ui:batchMode" : true, + "items": { + "ui:order" : [ + "key", + "type", + "field", + "getall", + "*" + ] + } + } +} diff --git a/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapterTest.java b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapterTest.java new file mode 100644 index 0000000000..7f62c0414d --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisPollingProtocolAdapterTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + + +import com.hivemq.adapter.sdk.api.model.ProtocolAdapterInput; +import com.hivemq.edge.adapters.redis.config.RedisAdapterConfig; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; + +import static org.mockito.Mockito.mock; + +class RedisPollingProtocolAdapterTest { + private final @NotNull ProtocolAdapterInput adapterInput = mock(); + private final @NotNull RedisAdapterConfig config = mock(); + + @Test + void test_poll_whenFileIsPresent_thenFileContentsAreSetInOutput() throws IOException { + // To be implemented + + } +} \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformationTest.java b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformationTest.java new file mode 100644 index 0000000000..29c7eb3a3e --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/RedisProtocolAdapterInformationTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + +import org.junit.jupiter.api.Test; + +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.*; + +class RedisProtocolAdapterInformationTest { + + @Test + void getProtocolId_MustNotContainWhiteSpaces() { + final RedisProtocolAdapterInformation information = new RedisProtocolAdapterInformation(); + assertFalse(information.getProtocolId().contains(" ")); + } + + + @Test + void getProtocolId_MustBeAlphaNummercialOrUnderscore() { + final String ALPHA_NUM = "[A-Za-z0-9_]*"; + final Pattern alphaNumPattern = Pattern.compile(ALPHA_NUM); + final RedisProtocolAdapterInformation information = new RedisProtocolAdapterInformation(); + assertTrue(alphaNumPattern.matcher(information.getProtocolId()).matches()); + } +} \ No newline at end of file diff --git a/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/TestPollingOutput.java b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/TestPollingOutput.java new file mode 100644 index 0000000000..e3d3eb1a7e --- /dev/null +++ b/modules/hivemq-edge-module-redis/src/test/java/com/hivemq/edge/adapters/redis/TestPollingOutput.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023-present HiveMQ GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hivemq.edge.adapters.redis; + +import com.hivemq.adapter.sdk.api.data.DataPoint; +import com.hivemq.adapter.sdk.api.polling.PollingOutput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class TestPollingOutput implements PollingOutput { + + private final @NotNull Map dataPoints = new HashMap<>(); + + final @NotNull CompletableFuture outputFuture = new CompletableFuture<>(); + private @Nullable String errorMessage = null; + + public TestPollingOutput() { + } + + @Override + public void addDataPoint(final @NotNull String tagName, final @NotNull Object tagValue) { + dataPoints.put(tagName, tagValue); + } + + @Override + public void addDataPoint(final @NotNull DataPoint dataPoint) { + // NOOP + } + + @Override + public void finish() { + outputFuture.complete(true); + } + + @Override + public void fail(final @NotNull Throwable t, @Nullable final String errorMessage) { + this.errorMessage = errorMessage; + outputFuture.completeExceptionally(t); + } + + @Override + public void fail(@NotNull final String errorMessage) { + this.errorMessage = errorMessage; + outputFuture.completeExceptionally(new RuntimeException()); + } + + public @NotNull CompletableFuture getOutputFuture() { + return outputFuture; + } + + public @NotNull Map getDataPoints() { + return dataPoints; + } + + public @Nullable String getErrorMessage() { + return errorMessage; + } +}