From debf9b1b54889a4905e729c48a7a368f9b8a56c3 Mon Sep 17 00:00:00 2001 From: krital Date: Wed, 8 Jul 2026 16:50:15 +0300 Subject: [PATCH 1/3] feat: add ServerAgentFactory SPI for extension-contributed agents --- .../io/mapsmessaging/ServerAgentFactory.java | 39 +++++++++++++++++++ .../io/mapsmessaging/SubSystemManager.java | 14 +++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/io/mapsmessaging/ServerAgentFactory.java diff --git a/src/main/java/io/mapsmessaging/ServerAgentFactory.java b/src/main/java/io/mapsmessaging/ServerAgentFactory.java new file mode 100644 index 000000000..1b9e6cc82 --- /dev/null +++ b/src/main/java/io/mapsmessaging/ServerAgentFactory.java @@ -0,0 +1,39 @@ +/* + * + * 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; + +import io.mapsmessaging.utilities.Agent; + +/** + * SPI allowing extension jars to contribute a lifecycle {@link Agent} to the server. + * Implementations are discovered via {@link java.util.ServiceLoader} at startup and + * started/stopped by {@link SubSystemManager} using the supplied ordering weights. + */ +public interface ServerAgentFactory { + + /** Create the agent instance to be managed by the server. */ + Agent create(); + + /** Start ordering weight; lower values start earlier. */ + int startOrder(); + + /** Stop ordering weight; lower values stop earlier. */ + int stopOrder(); +} diff --git a/src/main/java/io/mapsmessaging/SubSystemManager.java b/src/main/java/io/mapsmessaging/SubSystemManager.java index 4727cc9a1..6abcb7f86 100644 --- a/src/main/java/io/mapsmessaging/SubSystemManager.java +++ b/src/main/java/io/mapsmessaging/SubSystemManager.java @@ -258,6 +258,7 @@ private void createAgentStartStopList() throws IOException { addToMap(2200, 70, new DeviceManager(featureManager)); } addOptionalML(); + loadExtensionAgents(); } private void addOptionalML(){ @@ -275,6 +276,19 @@ private void addOptionalML(){ } } + private void loadExtensionAgents() { + ServiceLoader loader = ServiceLoader.load(ServerAgentFactory.class); + for (ServerAgentFactory factory : loader) { + try { + Agent agent = factory.create(); + addToMap(factory.startOrder(), factory.stopOrder(), agent); + logger.log(ServerLogMessages.MESSAGE_DAEMON_SERVICE_LOADED, agent.getName()); + } catch (Throwable t) { + logger.log(ServerLogMessages.MESSAGE_DAEMON_AGENT_FAILED, "extension agent", t); + } + } + } + /** From ee604dfc2c83c5eb55491fc775b2fe69fff7891e Mon Sep 17 00:00:00 2001 From: krital Date: Wed, 8 Jul 2026 17:04:40 +0300 Subject: [PATCH 2/3] fix: harden extension-agent loading (ServiceConfigurationError safety, non-fatal log) --- src/main/java/io/mapsmessaging/SubSystemManager.java | 10 ++++++++-- .../io/mapsmessaging/logging/ServerLogMessages.java | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/mapsmessaging/SubSystemManager.java b/src/main/java/io/mapsmessaging/SubSystemManager.java index 6abcb7f86..71cd20d91 100644 --- a/src/main/java/io/mapsmessaging/SubSystemManager.java +++ b/src/main/java/io/mapsmessaging/SubSystemManager.java @@ -278,13 +278,19 @@ private void addOptionalML(){ private void loadExtensionAgents() { ServiceLoader loader = ServiceLoader.load(ServerAgentFactory.class); - for (ServerAgentFactory factory : loader) { + for (Iterator iterator = loader.iterator(); iterator.hasNext(); ) { try { + ServerAgentFactory factory = iterator.next(); Agent agent = factory.create(); + if (agentMap.containsKey(agent.getName())) { + logger.log(ServerLogMessages.MESSAGE_DAEMON_EXTENSION_AGENT_FAILED, + agent.getName() + " (duplicate name, ignored)"); + continue; + } addToMap(factory.startOrder(), factory.stopOrder(), agent); logger.log(ServerLogMessages.MESSAGE_DAEMON_SERVICE_LOADED, agent.getName()); } catch (Throwable t) { - logger.log(ServerLogMessages.MESSAGE_DAEMON_AGENT_FAILED, "extension agent", t); + logger.log(ServerLogMessages.MESSAGE_DAEMON_EXTENSION_AGENT_FAILED, String.valueOf(t.getMessage())); } } } diff --git a/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java b/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java index 59ca02d79..98c5dbf58 100644 --- a/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java +++ b/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java @@ -63,6 +63,7 @@ public enum ServerLogMessages implements LogMessage { MESSAGE_DAEMON_AGENT_STARTING(LEVEL.AUDIT, SERVER_CATEGORY.DAEMON, "Starting {} "), MESSAGE_DAEMON_AGENT_STARTED(LEVEL.WARN, SERVER_CATEGORY.DAEMON, "Started {} took {}ms"), MESSAGE_DAEMON_AGENT_FAILED(LEVEL.FATAL, SERVER_CATEGORY.DAEMON, "Failed to start {}, exception {}, system exitting"), + MESSAGE_DAEMON_EXTENSION_AGENT_FAILED(LEVEL.WARN, SERVER_CATEGORY.DAEMON, "Failed to load extension agent, {}"), MESSAGE_DAEMON_AGENT_STOPPING(LEVEL.AUDIT, SERVER_CATEGORY.DAEMON, "Stopping {} "), MESSAGE_DAEMON_AGENT_STOPPED(LEVEL.WARN, SERVER_CATEGORY.DAEMON, "Stopped {} took {}ms"), From 975ac08fdbf1100519e7999247df591a4ffb0b05 Mon Sep 17 00:00:00 2001 From: krital Date: Fri, 10 Jul 2026 17:29:18 +0300 Subject: [PATCH 3/3] security: gate extension-agent loading behind an explicit -Dextension.agents.allowed allowlist --- .../java/io/mapsmessaging/SubSystemManager.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/io/mapsmessaging/SubSystemManager.java b/src/main/java/io/mapsmessaging/SubSystemManager.java index 71cd20d91..599464f1a 100644 --- a/src/main/java/io/mapsmessaging/SubSystemManager.java +++ b/src/main/java/io/mapsmessaging/SubSystemManager.java @@ -277,10 +277,23 @@ private void addOptionalML(){ } private void loadExtensionAgents() { + Set allowed = new HashSet<>(); + for (String entry : System.getProperty("extension.agents.allowed", "").split(",")) { + String trimmed = entry.trim(); + if (!trimmed.isEmpty()) { + allowed.add(trimmed); + } + } ServiceLoader loader = ServiceLoader.load(ServerAgentFactory.class); for (Iterator iterator = loader.iterator(); iterator.hasNext(); ) { try { ServerAgentFactory factory = iterator.next(); + String factoryClass = factory.getClass().getName(); + if (!allowed.contains(factoryClass)) { + logger.log(ServerLogMessages.MESSAGE_DAEMON_EXTENSION_AGENT_FAILED, + factoryClass + " (not listed in -Dextension.agents.allowed; skipped)"); + continue; + } Agent agent = factory.create(); if (agentMap.containsKey(agent.getName())) { logger.log(ServerLogMessages.MESSAGE_DAEMON_EXTENSION_AGENT_FAILED,