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..599464f1a 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,38 @@ 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, + 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_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"),