diff --git a/.buildkite/lora_pipeline.yml b/.buildkite/lora_pipeline.yml
index 2c3512cb1..d23101923 100644
--- a/.buildkite/lora_pipeline.yml
+++ b/.buildkite/lora_pipeline.yml
@@ -17,5 +17,5 @@ steps:
- trigger: "090-server-ml-snapshot-build"
- wait
-
+ - trigger: "100-server-3rd-party-extensions-snapshot"
- trigger: "200-build-windows-installer"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index c37f33840..c1a7d23bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,10 @@
+# Build outputs
target/
+*.class
+*.jar
+!**/src/**/lib/*.jar
+
+# Maven
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
@@ -9,4 +15,26 @@ buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
-.idea
+.m2_local
+
+# IDE / editor
+.idea/
+.vscode/
+*.iml
+*.ipr
+*.iws
+
+# Local / machine-specific
+.claude/settings.local.json
+
+# Logs and temp
+*.log
+*.log.gz
+*.tmp
+*.bak
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+.gitnexus
diff --git a/README.md b/README.md
index cb453630e..dcfcb7843 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,8 @@ Wire protocol standardization has promised interoperability and flexibility in I
- **Security Domains:** Configurable security domains allow for tailored authentication and authorization on a per-adapter/protocol basis.
- **Flexible Configuration:** Supports configuration through both Consul and file-based setups, catering to various deployment environments.
+Want to go deeper? [](https://deepwiki.com/Maps-Messaging/mapsmessaging_server)
+
## Getting Started: "Hello World" Example
This example demonstrates a simple publish/subscribe scenario using the MQTT protocol.
@@ -92,4 +94,4 @@ For full license terms, see the [LICENSE](LICENSE) file in the repository.
| Web Admin Client | [](https://buildkite.com/mapsmessaging/060-maps-web-client)| [](https://sonarcloud.io/summary/new_code?id=web-admin-client)|
-[](https://wiki.mutable.ai/Maps-Messaging/mapsmessaging_server)
\ No newline at end of file
+[](https://wiki.mutable.ai/Maps-Messaging/mapsmessaging_server)
diff --git a/pom.xml b/pom.xml
index 88964adb2..e11f547e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
io.mapsmessaging
maps
4.0.0
- 4.3.0
+ 4.3.1-SNAPSHOT
jar
Maps Messaging Server
@@ -87,9 +87,9 @@
${env.NVD_API_KEY}
UTF-8
- 1.5.21
+ 1.5.22
2.8.0
- 4.0.0
+ 4.0.1
3.17.0
**/*Suite.class
@@ -133,21 +133,25 @@
21
- 2.1.1
- 2.1.0
- ml-2.1.0
- 1.2.1
+ 2.1.2-SNAPSHOT
+ 1.0.1-SNAPSHOT
+ 2.1.1-SNAPSHOT
+ ml-2.1.1-SNAPSHOT
+ 1.2.2-SNAPSHOT
- 2.2.1
- 3.0.0
- 1.1.3
+ 1.0.1-SNAPSHOT
+ 2.2.2-SNAPSHOT
+ 3.0.1-SNAPSHOT
+ 1.1.5-SNAPSHOT
- 2.5.1
- 3.1.0
- 2.0.1
+ 2.5.2-SNAPSHOT
+ 3.1.1-SNAPSHOT
+ 3.0.2-SNAPSHOT
+ false
+ ${project.build.directory}/license
@@ -317,6 +321,28 @@
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.6.2
+
+
+ fetch-community-license
+ prepare-package
+
+ java
+
+
+ ${license.fetch.skip}
+ io.mapsmessaging.license.tools.LicenseFetcher
+ compile
+
+ ${license.fetch.dir}
+
+
+
+
+
org.apache.maven.plugins
@@ -394,6 +420,7 @@
**/*Test.java
+ **/*Tests.java
**/*IT.java
@@ -425,7 +452,7 @@
org.apache.maven.plugins
maven-source-plugin
- 3.3.1
+ 3.4.0
attach-sources
@@ -546,6 +573,24 @@
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.5.0
+
+
+ config-lint
+ verify
+
+ java
+
+
+ io.mapsmessaging.tools.config.lint.ConfigLintMain
+ compile
+
+
+
+
@@ -578,7 +623,7 @@
software.amazon.awssdk
bom
- 2.38.6
+ 2.41.13
pom
import
@@ -594,11 +639,41 @@
+
+ io.mapsmessaging
+ canbus-core
+ 1.0.0-SNAPSHOT
+
+
+
+ io.mapsmessaging
+ canbus-J1939
+ 1.0.0-SNAPSHOT
+
+
+
+ io.mapsmessaging
+ canbus-nmea2000
+ 1.0.0-SNAPSHOT
+
+
+
io.mapsmessaging
simple_logging
${maps.logging.version}
+
+ io.mapsmessaging
+ mavlink
+ ${maps.mavlink.version}
+
+
+ io.mapsmessaging
+ JsonQuery
+ ${maps.jsonquery.version}
+
+
io.mapsmessaging
jms_selector_parser
@@ -760,13 +835,13 @@
io.swagger.core.v3
swagger-jaxrs2-jakarta
- 2.2.40
+ 2.2.42
io.swagger.core.v3
swagger-jaxrs2-servlet-initializer-v2-jakarta
- 2.2.40
+ 2.2.42
@@ -882,7 +957,12 @@
cognitoidentityprovider
2.38.6
-
+
+
+ com.networknt
+ json-schema-validator
+ 2.0.0
+
@@ -986,7 +1066,7 @@
com.hivemq
hivemq-mqtt-client
- 1.3.10
+ 1.3.12
test
@@ -1117,12 +1197,48 @@
3.14.0
test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.21.0
+ test
+
org.eclipse.californium
scandium
3.14.0
test
+
+
+
+ io.rest-assured
+ rest-assured
+ 5.5.7
+ test
+
+
+ org.java-websocket
+ Java-WebSocket
+ 1.5.3
+
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ 4.12.0
+ test
+
+
+
+
+ com.atlassian.oai
+ swagger-request-validator-restassured
+ 2.46.0
+ test
+
+
diff --git a/src/main/assemble/scripts.xml b/src/main/assemble/scripts.xml
index 042d9a200..8e378429c 100644
--- a/src/main/assemble/scripts.xml
+++ b/src/main/assemble/scripts.xml
@@ -1,7 +1,7 @@
+
diff --git a/src/main/scripts/docker_run.sh b/src/main/scripts/docker_run.sh
index 1f3153d39..f7cc5ef4c 100644
--- a/src/main/scripts/docker_run.sh
+++ b/src/main/scripts/docker_run.sh
@@ -1,7 +1,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/download-extensions.ps1 b/src/main/scripts/download-extensions.ps1
new file mode 100644
index 000000000..4d27a564c
--- /dev/null
+++ b/src/main/scripts/download-extensions.ps1
@@ -0,0 +1,73 @@
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+$RepoBase = "https://repository.mapsmessaging.io/repository/maps_snapshots"
+
+function Get-LatestSnapshotJarValue {
+ param(
+ [Parameter(Mandatory=$true)][string]$MetadataXml,
+ [string]$Classifier = $null
+ )
+
+ [xml]$xml = $MetadataXml
+ $snapshotVersions = $xml.metadata.versioning.snapshotVersions.snapshotVersion
+ if (-not $snapshotVersions) {
+ throw "No snapshotVersions found in maven-metadata.xml"
+ }
+
+ if ([string]::IsNullOrWhiteSpace($Classifier)) {
+ $match = $snapshotVersions | Where-Object {
+ $_.extension -eq "jar" -and (-not $_.classifier -or $_.classifier -eq "")
+ } | Select-Object -First 1
+ } else {
+ $match = $snapshotVersions | Where-Object {
+ $_.extension -eq "jar" -and $_.classifier -eq $Classifier
+ } | Select-Object -First 1
+ }
+
+ if (-not $match) {
+ throw "No matching jar snapshotVersion found (classifier='$Classifier')"
+ }
+
+ return $match.value
+}
+
+function Download-LatestSnapshotJarAndRename {
+ param(
+ [Parameter(Mandatory=$true)][string]$GroupId,
+ [Parameter(Mandatory=$true)][string]$ArtifactId,
+ [Parameter(Mandatory=$true)][string]$Version, # e.g. 1.0.0-SNAPSHOT
+ [string]$Classifier = $null
+ )
+
+ $groupPath = $GroupId -replace "\.", "/"
+ $artifactDir = "$RepoBase/$groupPath/$ArtifactId/$Version"
+ $metadataUrl = "$artifactDir/maven-metadata.xml"
+
+ Write-Host "==> $GroupId`:$ArtifactId`:$Version$([string]::IsNullOrWhiteSpace($Classifier) ? "" : ":$Classifier")"
+ Write-Host " metadata: $metadataUrl"
+
+ $metadataXml = (Invoke-WebRequest -Uri $metadataUrl -UseBasicParsing).Content
+ $jarValue = Get-LatestSnapshotJarValue -MetadataXml $metadataXml -Classifier $Classifier
+
+ $timestampedFile = "$ArtifactId-$jarValue.jar"
+ $jarUrl = "$artifactDir/$timestampedFile"
+
+ $finalFile = "$ArtifactId-$Version.jar" # <-- the name you want
+
+ Write-Host " download: $jarUrl"
+ Invoke-WebRequest -Uri $jarUrl -OutFile $timestampedFile -UseBasicParsing
+
+ if (Test-Path $finalFile) { Remove-Item -Force $finalFile }
+ Rename-Item -Path $timestampedFile -NewName $finalFile
+
+ Write-Host " saved as: $finalFile"
+ Write-Host ""
+}
+
+
+# ---- Examples (fill in the exact artifactIds/versions you use) ----
+Download-LatestSnapshotJarAndRename -GroupId "io.mapsmessaging" -ArtifactId "aws-sns-extension" -Version "1.0.0-SNAPSHOT"
+Download-LatestSnapshotJarAndRename -GroupId "io.mapsmessaging" -ArtifactId "ibm-mq-extension" -Version "1.0.0-SNAPSHOT"
+Download-LatestSnapshotJarAndRename -GroupId "io.mapsmessaging" -ArtifactId "pulsar-extension" -Version "1.0.0-SNAPSHOT"
+Download-LatestSnapshotJarAndRename -GroupId "io.mapsmessaging" -ArtifactId "v2x-step-extension" -Version "1.0.0-SNAPSHOT"
diff --git a/src/main/scripts/download-extensions.sh b/src/main/scripts/download-extensions.sh
new file mode 100644
index 000000000..346fec4fb
--- /dev/null
+++ b/src/main/scripts/download-extensions.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+#
+#
+# Copyright [ 2020 - 2024 ] Matthew Buckton
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+#
+# Licensed under the Apache License, Version 2.0 with the Commons Clause
+# (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
+# https://commonsclause.com/
+#
+# 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.
+#
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_BASE="https://repository.mapsmessaging.io/repository/maps_snapshots"
+
+download_latest_snapshot_jar_and_rename() {
+ local group_id="$1"
+ local artifact_id="$2"
+ local version="$3" # e.g. 1.0.0-SNAPSHOT
+ local classifier="${4:-}" # optional
+
+ local group_path
+ group_path="$(echo "${group_id}" | tr '.' '/')"
+
+ local artifact_dir="${REPO_BASE}/${group_path}/${artifact_id}/${version}"
+ local metadata_url="${artifact_dir}/maven-metadata.xml"
+
+ echo "==> ${group_id}:${artifact_id}:${version}${classifier:+:${classifier}}"
+ echo " metadata: ${metadata_url}"
+
+ local metadata
+ metadata="$(curl -fsSL "${metadata_url}")"
+
+ local jar_value=""
+ if [[ -n "${classifier}" ]]; then
+ jar_value="$(echo "${metadata}" | awk -v cls="${classifier}" '
+ BEGIN { RS=""; FS="\n" }
+ $0 ~ // && $0 ~ "jar" && $0 ~ ""cls"" {
+ if (match($0, /[^<]+<\/value>/)) {
+ v=substr($0, RSTART+7, RLENGTH-15); print v; exit
+ }
+ }')"
+ else
+ jar_value="$(echo "${metadata}" | awk '
+ BEGIN { RS=""; FS="\n" }
+ $0 ~ // && $0 ~ "jar" && $0 !~ "" {
+ if (match($0, /[^<]+<\/value>/)) {
+ v=substr($0, RSTART+7, RLENGTH-15); print v; exit
+ }
+ }')"
+ fi
+
+ if [[ -z "${jar_value}" ]]; then
+ echo " ERROR: Could not find a jar snapshotVersion in metadata." >&2
+ exit 1
+ fi
+
+ local timestamped_file="${artifact_id}-${jar_value}.jar"
+ local jar_url="${artifact_dir}/${timestamped_file}"
+
+ local final_file="${artifact_id}-${version}.jar" # <-- the name you want
+
+ echo " download: ${jar_url}"
+ curl -fL --retry 3 --retry-delay 1 -o "${timestamped_file}" "${jar_url}"
+
+ mv -f "${timestamped_file}" "${final_file}"
+ echo " saved as: ${final_file}"
+ echo
+}
+
+# ---- Examples (fill in the exact artifactIds/versions you use) ----
+download_latest_snapshot_jar_and_rename "io.mapsmessaging" "aws-sns-extension" "1.0.0-SNAPSHOT"
+download_latest_snapshot_jar_and_rename "io.mapsmessaging" "ibm-mq-extension" "1.0.0-SNAPSHOT"
+download_latest_snapshot_jar_and_rename "io.mapsmessaging" "pulsar-extension" "1.0.0-SNAPSHOT"
+download_latest_snapshot_jar_and_rename "io.mapsmessaging" "v2x-step-extension" "1.0.0-SNAPSHOT"
diff --git a/src/main/scripts/download-smile.sh b/src/main/scripts/download-smile.sh
index 97589db57..703459d23 100644
--- a/src/main/scripts/download-smile.sh
+++ b/src/main/scripts/download-smile.sh
@@ -1,13 +1,9 @@
#!/bin/bash
-# This script downloads Smile ML libraries (GPL-3.0 licensed) from Maven Central.
-# You are responsible for complying with the Smile license (https://github.com/haifengl/smile).
-# MapsMessaging does not distribute Smile or bundle it directly.
-
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
@@ -23,6 +19,12 @@
# limitations under the License.
#
+# This script downloads Smile ML libraries (GPL-3.0 licensed) from Maven Central.
+# You are responsible for complying with the Smile license (https://github.com/haifengl/smile).
+# MapsMessaging does not distribute Smile or bundle it directly.
+
+
+
current_dir=$(pwd)
if [[ "$current_dir" == */bin ]]; then
parent_dir=$(dirname "$current_dir")
diff --git a/src/main/scripts/fail2ban/fail2ban-config.sh b/src/main/scripts/fail2ban/fail2ban-config.sh
new file mode 100644
index 000000000..c4bb17045
--- /dev/null
+++ b/src/main/scripts/fail2ban/fail2ban-config.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+#
+#
+# Copyright [ 2020 - 2024 ] Matthew Buckton
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+#
+# Licensed under the Apache License, Version 2.0 with the Commons Clause
+# (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
+# https://commonsclause.com/
+#
+# 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.
+#
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+FILTER_SRC="${SCRIPT_DIR}/maps-auth.conf"
+JAIL_SRC="${SCRIPT_DIR}/maps-auth.local"
+
+FILTER_DST="/etc/fail2ban/filter.d/maps-auth.conf"
+JAIL_DST="/etc/fail2ban/jail.d/maps-auth.local"
+
+if [[ "${EUID}" -ne 0 ]]; then
+ echo "This script must be run as root (use sudo)." >&2
+ exit 1
+fi
+
+if ! command -v fail2ban-client >/dev/null 2>&1; then
+ echo "fail2ban-client not found. Fail2ban does not appear to be installed." >&2
+ exit 1
+fi
+
+if [[ ! -f "${FILTER_SRC}" ]]; then
+ echo "Missing filter file: ${FILTER_SRC}" >&2
+ exit 1
+fi
+
+if [[ ! -f "${JAIL_SRC}" ]]; then
+ echo "Missing jail file: ${JAIL_SRC}" >&2
+ exit 1
+fi
+
+echo "Installing Fail2ban filter: ${FILTER_SRC} -> ${FILTER_DST}"
+install -m 0644 -D "${FILTER_SRC}" "${FILTER_DST}"
+
+echo "Installing Fail2ban jail override: ${JAIL_SRC} -> ${JAIL_DST}"
+install -m 0644 -D "${JAIL_SRC}" "${JAIL_DST}"
+
+echo "Validating Fail2ban configuration..."
+fail2ban-client -t
+
+echo "Reloading Fail2ban..."
+if systemctl is-active --quiet fail2ban; then
+ systemctl restart fail2ban
+else
+ fail2ban-client reload || true
+fi
+
+echo "Checking jail status for 'maps-auth'..."
+fail2ban-client status maps-auth
+
+
+echo "Done."
diff --git a/src/main/scripts/fail2ban/maps-auth.conf b/src/main/scripts/fail2ban/maps-auth.conf
new file mode 100644
index 000000000..551e9f8e1
--- /dev/null
+++ b/src/main/scripts/fail2ban/maps-auth.conf
@@ -0,0 +1,19 @@
+[Definition]
+# Match failures and lockouts emitted by MAPS auth monitor logging.
+# Assumes the log line contains one of these tokens and the client IP address.
+#
+# Example lines this will match (examples only):
+# ... AUTH_FAILURE user=bob attempts=3 ip=203.0.113.9
+# ... AUTH_LOCKOUT_STARTED user=bob attempts=5 lockSeconds=60 ip=203.0.113.9
+#
+# If your log format is different, adjust the tail so the IP is captured as .
+
+failregex =
+ ^.*\bAUTH_FAILURE\b.*\bip=\b.*$
+ ^.*\bAUTH_LOCKOUT_STARTED\b.*\bip=\b.*$
+ ^.*EndPoint closed during protocol negotiation.*\bip=\b.*$
+ ^.*Failed to detect protocol on End Point .*?,\s*\bip=\b.*$
+ ^.*Packet integrity verification failed:.*\breason=SIGNATURE_MISMATCH\b.*\bip=\b.*$
+
+ignoreregex = ^.*ip=127\..*$
+ ^.*ip=::1.*$
diff --git a/src/main/scripts/fail2ban/maps-auth.local b/src/main/scripts/fail2ban/maps-auth.local
new file mode 100644
index 000000000..52e74c8c3
--- /dev/null
+++ b/src/main/scripts/fail2ban/maps-auth.local
@@ -0,0 +1,14 @@
+[maps-auth]
+enabled = true
+filter = maps-auth
+
+# Point this at your MAPS log file
+logpath = /opt/maps_data/log/messaging.log
+
+# How aggressive you want it:
+findtime = 300
+maxretry = 5
+bantime = 3600
+
+# If your host is behind a proxy and logs proxy IPs, fix logging first.
+# action defaults to banning via firewall (nftables/iptables) depending on distro.
diff --git a/src/main/scripts/generate.sh b/src/main/scripts/generate.sh
index a6ccc42f2..36037b65d 100755
--- a/src/main/scripts/generate.sh
+++ b/src/main/scripts/generate.sh
@@ -2,7 +2,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/maps b/src/main/scripts/maps
index de76e4628..72634bdac 100644
--- a/src/main/scripts/maps
+++ b/src/main/scripts/maps
@@ -3,7 +3,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/mapsTop.sh b/src/main/scripts/mapsTop.sh
index 7e1e392d6..1cd50ddc6 100644
--- a/src/main/scripts/mapsTop.sh
+++ b/src/main/scripts/mapsTop.sh
@@ -1,7 +1,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/pack.sh b/src/main/scripts/pack.sh
index 10dc5bcb1..9dc983cdf 100755
--- a/src/main/scripts/pack.sh
+++ b/src/main/scripts/pack.sh
@@ -1,7 +1,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/start.sh b/src/main/scripts/start.sh
index 57fc9efcd..e08c6d34d 100644
--- a/src/main/scripts/start.sh
+++ b/src/main/scripts/start.sh
@@ -2,7 +2,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/main/scripts/startDocker.sh b/src/main/scripts/startDocker.sh
index 4c8c57ee7..a50a8a366 100644
--- a/src/main/scripts/startDocker.sh
+++ b/src/main/scripts/startDocker.sh
@@ -1,7 +1,7 @@
#
#
# Copyright [ 2020 - 2024 ] Matthew Buckton
-# Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+# Copyright [ 2024 - 2026 ] MapsMessaging B.V.
#
# Licensed under the Apache License, Version 2.0 with the Commons Clause
# (the "License"); you may not use this file except in compliance with the License.
@@ -38,11 +38,11 @@ export CONSUL_URL
echo $CONSUL_URL
if [ -z ${MAPS_HOME+x} ];
- then export MAPS_HOME=/maps-$VERSION;
+ then export MAPS_HOME=/opt/maps;
fi
if [ -z ${MAPS_DATA+x} ];
- then export MAPS_DATA=/data
+ then export MAPS_DATA=/opt/maps_data
fi
echo "Maps Home is set to '$MAPS_HOME'"
diff --git a/src/test/java/io/mapsmessaging/BaseTest.java b/src/test/java/io/mapsmessaging/BaseTest.java
index 9952e0c01..3652b69ce 100644
--- a/src/test/java/io/mapsmessaging/BaseTest.java
+++ b/src/test/java/io/mapsmessaging/BaseTest.java
@@ -1,7 +1,7 @@
/*
*
* Copyright [ 2020 - 2024 ] Matthew Buckton
- * Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
*
* Licensed under the Apache License, Version 2.0 with the Commons Clause
* (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/test/java/io/mapsmessaging/StoreFiller.java b/src/test/java/io/mapsmessaging/StoreFiller.java
index 58dacfa9f..9244292ef 100644
--- a/src/test/java/io/mapsmessaging/StoreFiller.java
+++ b/src/test/java/io/mapsmessaging/StoreFiller.java
@@ -1,7 +1,7 @@
/*
*
* Copyright [ 2020 - 2024 ] Matthew Buckton
- * Copyright [ 2024 - 2025 ] MapsMessaging B.V.
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
*
* Licensed under the Apache License, Version 2.0 with the Commons Clause
* (the "License"); you may not use this file except in compliance with the License.
diff --git a/src/test/java/io/mapsmessaging/aggregator/AggregatorSpec.java b/src/test/java/io/mapsmessaging/aggregator/AggregatorSpec.java
new file mode 100644
index 000000000..b31f16013
--- /dev/null
+++ b/src/test/java/io/mapsmessaging/aggregator/AggregatorSpec.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright [ 2020 - 2024 ] Matthew Buckton
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 with the Commons Clause
+ * (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
+ * https://commonsclause.com/
+ *
+ * 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 io.mapsmessaging.aggregator;
+
+import lombok.Data;
+
+@Data
+public class AggregatorSpec {
+
+ private final String outputTopic;
+ private final String in1;
+ private final String in2;
+ private final String in3;
+
+ private AggregatorSpec(String outputTopic, String in1, String in2, String in3) {
+ this.outputTopic = outputTopic;
+ this.in1 = in1;
+ this.in2 = in2;
+ this.in3 = in3;
+ }
+
+ public static AggregatorSpec aggregator1() {
+ return new AggregatorSpec(
+ "/aggregator1/out1",
+ "/aggregator1/in1",
+ "/aggregator1/in2",
+ "/aggregator1/in3"
+ );
+ }
+
+ public static AggregatorSpec aggregator2() {
+ return new AggregatorSpec(
+ "/aggregator2/out1",
+ "/aggregator2/in1",
+ "/aggregator2/in2",
+ "/aggregator2/in3"
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/io/mapsmessaging/aggregator/BaseAggreagtorTest.java b/src/test/java/io/mapsmessaging/aggregator/BaseAggreagtorTest.java
new file mode 100644
index 000000000..b31e079d0
--- /dev/null
+++ b/src/test/java/io/mapsmessaging/aggregator/BaseAggreagtorTest.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Copyright [ 2020 - 2024 ] Matthew Buckton
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 with the Commons Clause
+ * (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
+ * https://commonsclause.com/
+ *
+ * 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 io.mapsmessaging.aggregator;
+
+import io.mapsmessaging.api.*;
+import io.mapsmessaging.api.features.ClientAcknowledgement;
+import io.mapsmessaging.api.features.DestinationType;
+import io.mapsmessaging.api.features.QualityOfService;
+import io.mapsmessaging.api.message.Message;
+import io.mapsmessaging.engine.destination.subscription.SubscriptionContext;
+import io.mapsmessaging.logging.ServerLogMessages;
+import io.mapsmessaging.test.BaseTestConfig;
+
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BaseAggreagtorTest extends BaseTestConfig {
+
+
+ protected Session createSession(String sessionId, MessageListener listener) throws LoginException, IOException {
+ return createSession(sessionId, 60, 60, false, listener, true);
+ }
+
+ protected void closeSession(Session session) throws IOException {
+ super.close(session);
+ }
+
+ protected SubscribedEventManager addSubscription(String topicName, Session session) throws IOException {
+ SubscriptionContextBuilder scb = new SubscriptionContextBuilder(topicName, ClientAcknowledgement.AUTO);
+ SubscriptionContext context = scb.setReceiveMaximum(100).setQos(QualityOfService.AT_MOST_ONCE).build();
+ return session.addSubscription(context);
+ }
+
+ protected void closeSubscription(SubscribedEventManager subscription, Session session) {
+ session.removeSubscription(subscription.getContext().getKey());
+ }
+
+ protected void publish(String topicName, byte[] payload, Session session) throws ExecutionException, InterruptedException, TimeoutException {
+ MessageBuilder messageBuilder = new MessageBuilder();
+ messageBuilder.setOpaqueData(payload);
+ publish(topicName, messageBuilder.build(), session);
+ }
+
+ protected void publish(String topicName, Message message, Session session) throws ExecutionException, InterruptedException, TimeoutException {
+ session.findDestination(topicName, DestinationType.TOPIC).thenApply(destination -> {
+ try {
+ if (destination != null) {
+ destination.storeMessage(message);
+ }
+ } catch (IOException ioException) {
+ throw new RuntimeException(ioException);
+ }
+ return destination;
+ }).get(1, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/src/test/java/io/mapsmessaging/aggregator/Envelope.java b/src/test/java/io/mapsmessaging/aggregator/Envelope.java
new file mode 100644
index 000000000..8ba1d2026
--- /dev/null
+++ b/src/test/java/io/mapsmessaging/aggregator/Envelope.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright [ 2020 - 2024 ] Matthew Buckton
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 with the Commons Clause
+ * (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
+ * https://commonsclause.com/
+ *
+ * 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 io.mapsmessaging.aggregator;
+
+import lombok.Getter;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@Getter
+public class Envelope {
+
+ private final Map inputs;
+
+ public Envelope(Map inputs) {
+ this.inputs = inputs;
+ }
+
+ Object getPayloadField(String inputTopic, String fieldName) {
+ Object entryObj = inputs.get(inputTopic);
+ assertNotNull(entryObj, "Missing envelope entry for topic " + inputTopic);
+
+ @SuppressWarnings("unchecked")
+ Map entry = (Map) entryObj;
+
+ Object payloadObj = entry.get("payload");
+ assertNotNull(payloadObj, "Expected 'payload' object for topic " + inputTopic);
+
+ @SuppressWarnings("unchecked")
+ Map payload = (Map) payloadObj;
+
+ Object val = payload.get(fieldName);
+ assertNotNull(val, "Missing payload field '" + fieldName + "' for topic " + inputTopic);
+ return val;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/io/mapsmessaging/aggregator/StaticAggregatorOutboundTransformationSystemTest.java b/src/test/java/io/mapsmessaging/aggregator/StaticAggregatorOutboundTransformationSystemTest.java
new file mode 100644
index 000000000..477dedee0
--- /dev/null
+++ b/src/test/java/io/mapsmessaging/aggregator/StaticAggregatorOutboundTransformationSystemTest.java
@@ -0,0 +1,245 @@
+/*
+ *
+ * Copyright [ 2020 - 2024 ] Matthew Buckton
+ * Copyright [ 2024 - 2026 ] MapsMessaging B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 with the Commons Clause
+ * (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
+ * https://commonsclause.com/
+ *
+ * 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 io.mapsmessaging.aggregator;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import io.mapsmessaging.api.MessageBuilder;
+import io.mapsmessaging.api.MessageEvent;
+import io.mapsmessaging.api.MessageListener;
+import io.mapsmessaging.api.Session;
+import io.mapsmessaging.api.SubscribedEventManager;
+import io.mapsmessaging.api.message.Message;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Outbound transformation pipeline tests for aggregator-3.
+ *
+ * Intent:
+ * - Publish already-clean JSON inputs on /aggregator3/in1, /in2, /in3.
+ * - Aggregator builds its normal envelope internally, then outbound transformers reshape it.
+ * - Expected final output is a flat JSON object containing:
+ * { "runId": "...", "temp": 20, "humidity": 60, "pressure": 990 }
+ *
+ * This test is expected to FAIL until aggregator-3 outbound transformers are configured.
+ */
+class StaticAggregatorOutboundTransformationSystemTest extends BaseAggreagtorTest {
+
+ private static final long COMPLETE_PUBLISH_DEADLINE_MS = 500;
+ private static final String JSON_CONTENT_TYPE = "application/json";
+
+ private static final String OUT_TOPIC = "/aggregator3/out1";
+ private static final String IN1 = "/aggregator3/in1";
+ private static final String IN2 = "/aggregator3/in2";
+ private static final String IN3 = "/aggregator3/in3";
+
+ private final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
+ private final Type mapType = new TypeToken