From d160ca1dc81e8d2de269eb88393e7acfddc4e5de Mon Sep 17 00:00:00 2001 From: Joshua Buckton Date: Wed, 24 Jun 2026 11:40:46 +1000 Subject: [PATCH 1/4] refactor(state): load STANAG through state adapters Add a ServiceLoader-based state adapter SPI and move protocol-specific STANAG handling out to mapsmessaging-stanag-state-adapter. State adapter configuration now lives under an explicit stateAdapters section, keyed by adapter name. The server keeps that configuration opaque and adapters parse their own blocks. Expose generic audit primitives through StateAuditContext so adapters can construct protocol-specific auditors without server-owned STANAG types. --- pom.xml | 5 + ...StanagHelper.java => StateJsonHelper.java} | 35 +-- .../state/StateManagerAgent.java | 28 +- .../StateMessageAdapter.java} | 16 +- .../StateMessageAdapterContext.java} | 22 +- .../StateMessageAdapterFactory.java} | 11 +- .../state/auditor/AuditorFactory.java | 13 +- .../StateAuditContext.java} | 15 +- .../state/config/StanagConfig.java | 34 --- .../state/config/TwinManagerConfig.java | 48 ++- .../state/config/TwinManagerConfigDTO.java | 9 +- .../state/drone/core/TwinManager.java | 10 +- .../drone/publisher/TwinJsonPublisher.java | 6 +- .../state/stanag/ChatListener.java | 39 --- .../state/stanag/NodeUpdate.java | 199 ------------- .../state/stanag/StanagSession.java | 236 --------------- .../state/stanag/StanagUuidGenerator.java | 46 --- .../state/stanag/TaskAdminCommand.java | 150 ---------- .../stanag/TaskAdminCommandException.java | 31 -- .../state/stanag/TaskListener.java | 277 ------------------ .../state/stanag/audit/AuditEvent.java | 69 ----- .../state/stanag/audit/AuditMessages.java | 81 ----- .../state/stanag/audit/AuditPayload.java | 36 --- .../state/stanag/audit/Auditor.java | 217 -------------- .../stanag/messages/TaskEventPublisher.java | 86 ------ .../stanag/messages/TaskSchemaValidator.java | 52 ---- .../state/stanag/messages/TaskState.java | 53 ---- .../stanag/messages/TaskStatusContext.java | 27 -- .../stanag/messages/core/MessageHeader.java | 40 --- .../messages/core/MessageHeaderBuilder.java | 52 ---- .../stanag/messages/core/MessageType.java | 33 --- .../node/common/EntityDescription.java | 70 ----- .../messages/node/common/EulerAngles.java | 34 --- .../common/LatitudeLongitudeAltitude.java | 36 --- .../node/common/NodeMessageSupport.java | 146 --------- .../messages/node/common/Orientation.java | 35 --- .../stanag/messages/node/common/Pose.java | 32 -- .../stanag/messages/node/common/Position.java | 35 --- .../node/common/SpeedCourseClimbRate.java | 36 --- .../stanag/messages/node/common/Velocity.java | 35 --- .../node/description/NodeDescription.java | 34 --- .../node/description/NodeDescriptionBody.java | 38 --- .../description/NodeDescriptionBuilder.java | 42 --- .../messages/node/dynamic/DynamicUpdate.java | 14 - .../node/dynamic/DynamicUpdateBody.java | 12 - .../node/dynamic/DynamicUpdateBuilder.java | 146 --------- .../node/dynamic/DynamicUpdateOperation.java | 16 - .../messages/node/dynamic/PutValue.java | 15 - .../stanag/messages/node/dynamic/Track.java | 38 --- .../messages/node/status/NodeStatus.java | 33 --- .../messages/node/status/NodeStatusBody.java | 35 --- .../node/status/NodeStatusBuilder.java | 41 --- .../task/admin/TaskAdminActionEnum.java | 11 - .../messages/task/admin/TaskAdminBody.java | 20 -- .../messages/task/admin/TaskAdminMessage.java | 14 - .../task/admin/TaskAdminMessageBuilder.java | 34 --- .../task/feedback/TaskFeedbackBody.java | 31 -- .../task/feedback/TaskFeedbackMessage.java | 14 - .../feedback/TaskFeedbackMessageBuilder.java | 89 ------ .../messages/task/result/ResultReason.java | 15 - .../task/result/ResultReasonBuilder.java | 17 -- .../messages/task/result/TaskResultBody.java | 26 -- .../task/result/TaskResultMessage.java | 14 - .../task/result/TaskResultMessageBuilder.java | 68 ----- .../stanag/tasks/RepositionTaskHandler.java | 104 ------- .../stanag/tasks/TaskDispatchResult.java | 53 ---- .../state/stanag/tasks/TaskDispatcher.java | 150 ---------- .../state/stanag/tasks/TaskHandler.java | 78 ----- .../tasks/monitor/CompletedTaskMonitor.java | 47 --- .../tasks/monitor/RepositionTaskMonitor.java | 194 ------------ .../stanag/tasks/monitor/TaskMonitor.java | 203 ------------- .../tasks/monitor/TaskMonitorManager.java | 168 ----------- .../tasks/monitor/TaskStatusPublisher.java | 30 -- ...psmessaging.state.stanag.tasks.TaskHandler | 20 -- 74 files changed, 115 insertions(+), 4184 deletions(-) rename src/main/java/io/mapsmessaging/state/{GsonStanagHelper.java => StateJsonHelper.java} (66%) rename src/main/java/io/mapsmessaging/state/{stanag/tasks/monitor/TaskMonitorState.java => adapter/StateMessageAdapter.java} (81%) rename src/main/java/io/mapsmessaging/state/{stanag/messages/node/common/Altitude.java => adapter/StateMessageAdapterContext.java} (62%) rename src/main/java/io/mapsmessaging/state/{stanag/TaskMessageSender.java => adapter/StateMessageAdapterFactory.java} (77%) rename src/main/java/io/mapsmessaging/state/{stanag/messages/FlexibleEnumeration.java => auditor/StateAuditContext.java} (76%) delete mode 100644 src/main/java/io/mapsmessaging/state/config/StanagConfig.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/ChatListener.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/NodeUpdate.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/StanagSession.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/StanagUuidGenerator.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommand.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommandException.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/TaskListener.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/audit/AuditEvent.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/audit/AuditMessages.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/audit/AuditPayload.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/audit/Auditor.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/TaskEventPublisher.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/TaskSchemaValidator.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/TaskState.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/TaskStatusContext.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeader.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeaderBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageType.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EntityDescription.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EulerAngles.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/LatitudeLongitudeAltitude.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/NodeMessageSupport.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Orientation.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Pose.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Position.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/SpeedCourseClimbRate.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Velocity.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescription.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdate.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateOperation.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/PutValue.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/Track.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatus.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminActionEnum.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessage.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessageBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessage.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessageBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReason.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReasonBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultBody.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessage.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessageBuilder.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/RepositionTaskHandler.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatchResult.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatcher.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/TaskHandler.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/CompletedTaskMonitor.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/RepositionTaskMonitor.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitor.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorManager.java delete mode 100644 src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskStatusPublisher.java delete mode 100644 src/main/resources/META-INF/services/io.mapsmessaging.state.stanag.tasks.TaskHandler diff --git a/pom.xml b/pom.xml index 9d6d98d5b..5304d765a 100644 --- a/pom.xml +++ b/pom.xml @@ -681,6 +681,11 @@ + + io.mapsmessaging + mapsmessaging-stanag-state-adapter + ${project.version} + io.mapsmessaging diff --git a/src/main/java/io/mapsmessaging/state/GsonStanagHelper.java b/src/main/java/io/mapsmessaging/state/StateJsonHelper.java similarity index 66% rename from src/main/java/io/mapsmessaging/state/GsonStanagHelper.java rename to src/main/java/io/mapsmessaging/state/StateJsonHelper.java index bd2313dac..ae6bb0d5b 100644 --- a/src/main/java/io/mapsmessaging/state/GsonStanagHelper.java +++ b/src/main/java/io/mapsmessaging/state/StateJsonHelper.java @@ -24,17 +24,16 @@ import io.mapsmessaging.rest.translation.GsonDateTimeDeserialiser; import io.mapsmessaging.rest.translation.GsonDateTimeSerialiser; import io.mapsmessaging.rest.translation.InstantTypeAdapter; -import io.mapsmessaging.state.config.capability.*; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import io.mapsmessaging.state.stanag.messages.task.admin.TaskAdminActionEnum; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.messages.TaskState; - +import io.mapsmessaging.state.config.capability.PlanTaskType; +import io.mapsmessaging.state.config.capability.PrefixedEnumTypeAdapter; +import io.mapsmessaging.state.config.capability.TaskConditionMode; +import io.mapsmessaging.state.config.capability.TaskSpecialization; +import io.mapsmessaging.state.config.capability.TaskTemplateMode; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; -public class GsonStanagHelper { +public class StateJsonHelper { public static Gson createGson() { return new GsonBuilder() @@ -60,30 +59,10 @@ public static Gson createGson() { TaskTemplateMode.class, new PrefixedEnumTypeAdapter<>(TaskTemplateMode.class, "TaskTemplateModeEnum_") ) - .registerTypeAdapter( - MessageType.class, - new PrefixedEnumTypeAdapter<>(MessageType.class, "MessageTypeEnum_") - ) - .registerTypeAdapter( - TaskState.class, - new PrefixedEnumTypeAdapter<>(TaskState.class, "TaskStateEnum_") - ) - .registerTypeAdapter( - ResultReason.class, - new PrefixedEnumTypeAdapter<>(ResultReason.class, "ResultReasonEnum_") - ) - .registerTypeAdapter( - TaskAdminActionEnum.class, - new PrefixedEnumTypeAdapter<>(TaskAdminActionEnum.class, "TaskAdminActionEnum_") - ) - .registerTypeAdapter( - ResultReason.class, - new PrefixedEnumTypeAdapter<>(ResultReason.class, "ResultReasonEnum_") - ) .create(); } - private GsonStanagHelper() { + private StateJsonHelper() { // helper only } } diff --git a/src/main/java/io/mapsmessaging/state/StateManagerAgent.java b/src/main/java/io/mapsmessaging/state/StateManagerAgent.java index ba8ad184a..69b9d9a34 100644 --- a/src/main/java/io/mapsmessaging/state/StateManagerAgent.java +++ b/src/main/java/io/mapsmessaging/state/StateManagerAgent.java @@ -29,10 +29,11 @@ import io.mapsmessaging.dto.rest.system.SubSystemStatusDTO; import io.mapsmessaging.logging.Logger; import io.mapsmessaging.logging.LoggerFactory; +import io.mapsmessaging.state.adapter.StateMessageAdapterContext; +import io.mapsmessaging.state.adapter.StateMessageAdapterFactory; +import io.mapsmessaging.state.auditor.StateAuditContext; import io.mapsmessaging.state.drone.core.TwinLifecycleStatus; import io.mapsmessaging.state.drone.core.TwinManager; -import io.mapsmessaging.state.stanag.StanagSession; -import io.mapsmessaging.state.stanag.audit.Auditor; import io.mapsmessaging.utilities.Agent; import io.mapsmessaging.utilities.Lifecycle; import io.mapsmessaging.utilities.configuration.ConfigurationManager; @@ -42,6 +43,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.ServiceLoader; import static io.mapsmessaging.logging.ServerLogMessages.*; @@ -59,32 +61,28 @@ public class StateManagerAgent implements Agent { private TwinManager twinManager; @Getter - private Auditor auditor; + private StateAuditContext auditContext; private AuditorFactory.AuditorInstance auditorInstance; public StateManagerAgent() { TwinManagerConfigDTO config = ConfigurationManager.getInstance().getConfiguration(TwinManagerConfig.class); DroneInfoRegistry registry; TakProtocolDTO takConfig; - StanagConfig stanagConfig; if(config != null){ try { AuditorFactory factory = new AuditorFactory(); auditorInstance = factory.build(Path.of( EnvironmentConfig.getInstance().getPathLookups().get("MAPS_DATA"))); - auditor = auditorInstance.getAuditor(); + auditContext = auditorInstance.getAuditContext(); } catch (IOException e) { e.printStackTrace(); } - twinManager = new TwinManager(config.isRemoveExpiredTwins(), config.getStaleTimeoutMillis(), config.getHeartbeatTimeoutMillis(), config.getRetentionTimeoutMillis(), auditor); + twinManager = new TwinManager(config.isRemoveExpiredTwins(), config.getStaleTimeoutMillis(), config.getHeartbeatTimeoutMillis(), config.getRetentionTimeoutMillis(), auditContext); registry = new DroneInfoRegistry(config.getDroneInfo()); takConfig = config.getTak(); - stanagConfig = config.getStanagConfig(); lifecycleList.add(new SchedulerManager(twinManager)); lifecycleList.add(new TakManager(twinManager, takConfig)); lifecycleList.add(new MavlinkTwinManager(twinManager, registry, config)); - if(stanagConfig != null && stanagConfig.isEnable()) { - lifecycleList.add(new StanagSession(twinManager, stanagConfig)); - } + loadStateMessageAdapters(config); lifecycleList.add(new TwinPublisherManager(twinManager, config.getPublish())); aisManager = new AISN2KManager(twinManager, config.getN2KTwinConfig()); lifecycleList.add(aisManager); @@ -104,6 +102,14 @@ public String getDescription() { return "Manages state of known objects within memory and maintains a digital twin"; } + private void loadStateMessageAdapters(TwinManagerConfigDTO config) { + StateMessageAdapterContext context = new StateMessageAdapterContext(twinManager, config); + ServiceLoader adapterFactories = ServiceLoader.load(StateMessageAdapterFactory.class); + for (StateMessageAdapterFactory adapterFactory : adapterFactories) { + adapterFactory.create(context).ifPresent(lifecycleList::add); + } + } + @Override public synchronized void start() { try { @@ -141,4 +147,4 @@ public SubSystemStatusDTO getStatus() { status.setStatus(Status.OK); return status; } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorState.java b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapter.java similarity index 81% rename from src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorState.java rename to src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapter.java index 56ac571bb..fcd3c3910 100644 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorState.java +++ b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapter.java @@ -17,13 +17,11 @@ * limitations under the License. */ -package io.mapsmessaging.state.stanag.tasks.monitor; +package io.mapsmessaging.state.adapter; -public enum TaskMonitorState { - ACCEPTED, - IN_PROGRESS, - COMPLETE, - FAILED, - TIMEOUT, - LOST -} \ No newline at end of file +import io.mapsmessaging.utilities.Lifecycle; + +public interface StateMessageAdapter extends Lifecycle { + + String getName(); +} diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Altitude.java b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterContext.java similarity index 62% rename from src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Altitude.java rename to src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterContext.java index ff58d2159..c80afe0d9 100644 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Altitude.java +++ b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterContext.java @@ -17,16 +17,24 @@ * limitations under the License. */ -package io.mapsmessaging.state.stanag.messages.node.common; +package io.mapsmessaging.state.adapter; -import lombok.Builder; +import io.mapsmessaging.state.config.TwinManagerConfigDTO; +import io.mapsmessaging.state.drone.core.TwinManager; import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; @Getter -@Builder -public class Altitude { +@RequiredArgsConstructor +public class StateMessageAdapterContext { - private final Double value; + @NonNull + @NotNull + private final TwinManager twinManager; - private final String type; -} \ No newline at end of file + @NonNull + @NotNull + private final TwinManagerConfigDTO config; +} diff --git a/src/main/java/io/mapsmessaging/state/stanag/TaskMessageSender.java b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterFactory.java similarity index 77% rename from src/main/java/io/mapsmessaging/state/stanag/TaskMessageSender.java rename to src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterFactory.java index 21515b066..552a717ac 100644 --- a/src/main/java/io/mapsmessaging/state/stanag/TaskMessageSender.java +++ b/src/main/java/io/mapsmessaging/state/adapter/StateMessageAdapterFactory.java @@ -17,10 +17,13 @@ * limitations under the License. */ -package io.mapsmessaging.state.stanag; +package io.mapsmessaging.state.adapter; -import io.mapsmessaging.api.message.Message; +import java.util.Optional; -public interface TaskMessageSender { - void sendTaskMessage(String taskTopic, Message message); +public interface StateMessageAdapterFactory { + + String getName(); + + Optional create(StateMessageAdapterContext context); } diff --git a/src/main/java/io/mapsmessaging/state/auditor/AuditorFactory.java b/src/main/java/io/mapsmessaging/state/auditor/AuditorFactory.java index 3cd8590ac..c5579c3f9 100644 --- a/src/main/java/io/mapsmessaging/state/auditor/AuditorFactory.java +++ b/src/main/java/io/mapsmessaging/state/auditor/AuditorFactory.java @@ -33,7 +33,6 @@ import java.security.PrivateKey; import java.security.interfaces.EdECPublicKey; -import io.mapsmessaging.state.stanag.audit.Auditor; import lombok.Getter; public class AuditorFactory { @@ -110,13 +109,13 @@ public AuditorInstance build( AuditLogger auditLogger = new AuditLogger(auditJournal); AuditPayloadStore auditPayloadStore = new AuditPayloadStore(payloadRootDirectory); - Auditor auditor = new Auditor( + StateAuditContext auditContext = new StateAuditContext( auditLogger, auditPayloadStore ); return new AuditorInstance( - auditor, + auditContext, auditJournal, auditRootDirectory, journalRootDirectory, @@ -179,7 +178,7 @@ private record AuditKeys( @Getter public static class AuditorInstance implements AutoCloseable { - private final Auditor auditor; + private final StateAuditContext auditContext; private final AuditJournal auditJournal; private final Path auditRootDirectory; private final Path journalRootDirectory; @@ -188,7 +187,7 @@ public static class AuditorInstance implements AutoCloseable { private final Path publicKeyPath; private AuditorInstance( - Auditor auditor, + StateAuditContext auditContext, AuditJournal auditJournal, Path auditRootDirectory, Path journalRootDirectory, @@ -196,7 +195,7 @@ private AuditorInstance( Path privateKeyPath, Path publicKeyPath ) { - this.auditor = auditor; + this.auditContext = auditContext; this.auditJournal = auditJournal; this.auditRootDirectory = auditRootDirectory; this.journalRootDirectory = journalRootDirectory; @@ -210,4 +209,4 @@ public void close() throws IOException { auditJournal.close(); } } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/FlexibleEnumeration.java b/src/main/java/io/mapsmessaging/state/auditor/StateAuditContext.java similarity index 76% rename from src/main/java/io/mapsmessaging/state/stanag/messages/FlexibleEnumeration.java rename to src/main/java/io/mapsmessaging/state/auditor/StateAuditContext.java index f401b1c59..4535e6445 100644 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/FlexibleEnumeration.java +++ b/src/main/java/io/mapsmessaging/state/auditor/StateAuditContext.java @@ -17,15 +17,10 @@ * limitations under the License. */ -package io.mapsmessaging.state.stanag.messages; +package io.mapsmessaging.state.auditor; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; +import io.mapsmessaging.audit.AuditLogger; +import io.mapsmessaging.audit.AuditPayloadStore; -@Getter -@Setter -@AllArgsConstructor -public class FlexibleEnumeration { - private String name; -} \ No newline at end of file +public record StateAuditContext(AuditLogger auditLogger, AuditPayloadStore auditPayloadStore) { +} diff --git a/src/main/java/io/mapsmessaging/state/config/StanagConfig.java b/src/main/java/io/mapsmessaging/state/config/StanagConfig.java deleted file mode 100644 index 2c867a013..000000000 --- a/src/main/java/io/mapsmessaging/state/config/StanagConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * - * 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.state.config; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class StanagConfig { - private boolean enable = false; - private String taskTopic; - private String chatTopic; - private String taskTopicTemplate; - private int descriptionIntervalSec = 30; - private int statusIntervalSec = 5; -} diff --git a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java index 2a5b939ea..8ad718b38 100644 --- a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java +++ b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java @@ -33,6 +33,7 @@ import io.mapsmessaging.utilities.configuration.ConfigurationManager; import java.io.IOException; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; @@ -41,6 +42,8 @@ @NoArgsConstructor public class TwinManagerConfig extends TwinManagerConfigDTO implements Config, ConfigManager { + private static final String STATE_ADAPTERS_CONFIG_KEY = "stateAdapters"; + private TwinManagerConfig(ConfigurationProperties properties) { this.heartbeatTimeoutMillis = properties.getLongProperty("heartbeatTimeoutMillis", heartbeatTimeoutMillis); this.staleTimeoutMillis = properties.getLongProperty("staleTimeoutMillis", staleTimeoutMillis); @@ -74,21 +77,8 @@ private TwinManagerConfig(ConfigurationProperties properties) { this.mavlink = parseMavlinkConfigs(properties.get("mavlink")); } - if(properties.containsKey("stanag")) { - ConfigurationProperties stanagProps = (ConfigurationProperties) properties.get("stanag"); - this.stanagConfig = new StanagConfig(); - stanagConfig.setEnable(stanagProps.getBooleanProperty("enabled", stanagConfig.isEnable())); - stanagConfig.setChatTopic(stanagProps.getProperty("chatTopic", null)); - stanagConfig.setTaskTopic(stanagProps.getProperty("taskTopic", null)); - stanagConfig.setTaskTopicTemplate(stanagProps.getProperty("taskTopicTemplate", null)); - stanagConfig.setDescriptionIntervalSec(stanagProps.getIntProperty("descriptionIntervalSec", stanagConfig.getDescriptionIntervalSec())); - stanagConfig.setStatusIntervalSec(stanagProps.getIntProperty("statusIntervalSec", stanagConfig.getStatusIntervalSec())); - } - else{ - stanagConfig = new StanagConfig(); - stanagConfig.setChatTopic(null); - stanagConfig.setTaskTopic(null); - stanagConfig.setTaskTopicTemplate(null); + if (properties.containsKey(STATE_ADAPTERS_CONFIG_KEY)) { + parseAdapterConfigs(properties.get(STATE_ADAPTERS_CONFIG_KEY)); } n2KTwinConfig = new N2KTwinConfig(); @@ -154,6 +144,11 @@ public ConfigurationProperties toConfigurationProperties() { if (this.mavlink != null && !this.mavlink.isEmpty()) { props.put("mavlink", toMavlinkConfigurationProperties(this.mavlink)); } + + if (this.adapterConfig != null && !this.adapterConfig.isEmpty()) { + props.put(STATE_ADAPTERS_CONFIG_KEY, new ConfigurationProperties(new LinkedHashMap<>(this.adapterConfig))); + } + ConfigurationProperties n2kProps = new ConfigurationProperties(); n2kProps.put("publishMavlinkDrones", this.n2KTwinConfig.isPublishMavlinkDrones()); n2kProps.put("ais", n2KTwinConfig.getAis()); @@ -232,9 +227,30 @@ public boolean update(BaseConfigDTO config) { hasChanged = true; } + if (this.adapterConfig != newConfig.getAdapterConfig()) { + this.adapterConfig = new LinkedHashMap<>(newConfig.getAdapterConfig()); + hasChanged = true; + } + return hasChanged; } + private void parseAdapterConfigs(Object value) { + if (value instanceof ConfigurationProperties properties) { + for (var entry : properties.entrySet()) { + if (entry.getValue() instanceof ConfigurationProperties adapterProperties) { + this.adapterConfig.put(entry.getKey(), adapterProperties); + } + } + } else if(value instanceof java.util.Map entries) { + for (var entry : entries.entrySet()) { + if (entry.getKey() instanceof String adapterName && entry.getValue() instanceof ConfigurationProperties adapterProperties) { + this.adapterConfig.put(adapterName, adapterProperties); + } + } + } + } + private List parseMavlinkConfigs(Object value) { List configs = new ArrayList<>(); @@ -509,4 +525,4 @@ private List toKnownSourceConfigurationProperties(List< return values; } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfigDTO.java b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfigDTO.java index ea4961d65..d0c9e943e 100644 --- a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfigDTO.java +++ b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfigDTO.java @@ -21,13 +21,16 @@ import io.mapsmessaging.dto.rest.config.BaseManagerConfigDTO; import io.mapsmessaging.dto.rest.config.protocol.impl.TakProtocolDTO; +import io.mapsmessaging.configuration.ConfigurationProperties; import io.mapsmessaging.state.config.n2k.N2KTwinConfig; import lombok.Data; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @@ -101,9 +104,9 @@ public class TwinManagerConfigDTO extends BaseManagerConfigDTO { @Schema( - description = "Stanag configuration" + description = "Protocol-specific state message adapter configuration keyed by adapter name" ) - protected StanagConfig stanagConfig; + protected Map adapterConfig = new LinkedHashMap<>(); @Schema( description = "Configuration on each drone" @@ -122,4 +125,4 @@ public TwinManagerConfigDTO() { public String getSimpleName() { return "State Cache"; } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/drone/core/TwinManager.java b/src/main/java/io/mapsmessaging/state/drone/core/TwinManager.java index a5705154d..25197bf2a 100644 --- a/src/main/java/io/mapsmessaging/state/drone/core/TwinManager.java +++ b/src/main/java/io/mapsmessaging/state/drone/core/TwinManager.java @@ -5,7 +5,7 @@ import io.mapsmessaging.logging.Logger; import io.mapsmessaging.logging.LoggerFactory; -import io.mapsmessaging.state.stanag.audit.Auditor; +import io.mapsmessaging.state.auditor.StateAuditContext; import lombok.Getter; import java.time.Instant; @@ -27,7 +27,7 @@ public class TwinManager { private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList<>(); private final Logger logger = LoggerFactory.getLogger(TwinManager.class); @Getter - private final Auditor auditor; + private final StateAuditContext auditContext; private final boolean removeExpiredTwins; private final long staleTimeoutMillis; private final long heartbeatTimeoutMillis; @@ -37,8 +37,8 @@ public TwinManager() { this(true, 10000L, 5000L, 120000L, null); } - public TwinManager(boolean removeExpiredTwins, long staleTimeoutMillis, long heartbeatTimeoutMillis, long retentionTimeoutMillis, Auditor auditor) { - this.auditor = auditor; + public TwinManager(boolean removeExpiredTwins, long staleTimeoutMillis, long heartbeatTimeoutMillis, long retentionTimeoutMillis, StateAuditContext auditContext) { + this.auditContext = auditContext; this.removeExpiredTwins = removeExpiredTwins; this.staleTimeoutMillis = staleTimeoutMillis; this.heartbeatTimeoutMillis = heartbeatTimeoutMillis; @@ -388,4 +388,4 @@ private void notifyRelationshipRemoved(String twinId, } } } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/drone/publisher/TwinJsonPublisher.java b/src/main/java/io/mapsmessaging/state/drone/publisher/TwinJsonPublisher.java index 449c2ec31..d893b08b5 100644 --- a/src/main/java/io/mapsmessaging/state/drone/publisher/TwinJsonPublisher.java +++ b/src/main/java/io/mapsmessaging/state/drone/publisher/TwinJsonPublisher.java @@ -32,7 +32,7 @@ import io.mapsmessaging.api.features.QualityOfService; import io.mapsmessaging.engine.schema.SchemaManager; import io.mapsmessaging.engine.session.ClientConnection; -import io.mapsmessaging.state.GsonStanagHelper; +import io.mapsmessaging.state.StateJsonHelper; import io.mapsmessaging.state.drone.core.EntityTwin; import io.mapsmessaging.state.drone.core.TwinManager; import io.mapsmessaging.state.drone.core.TwinObserver; @@ -64,7 +64,7 @@ public class TwinJsonPublisher implements TwinObserver, ClientConnection, Messag public TwinJsonPublisher(TwinManager twinManager, String topicTemplate) throws ExecutionException, InterruptedException, TimeoutException { this.topicTemplate = topicTemplate; this.twinManager = twinManager; - this.gson = GsonStanagHelper.createGson(); + this.gson = StateJsonHelper.createGson(); this.destinationCache = new ConcurrentHashMap<>(); this.session = createSession(); twinManager.addObserver(this); @@ -224,4 +224,4 @@ public String getRemoteIp() { public void sendMessage(@NotNull @NonNull MessageEvent messageEvent) { // no-op } -} \ No newline at end of file +} diff --git a/src/main/java/io/mapsmessaging/state/stanag/ChatListener.java b/src/main/java/io/mapsmessaging/state/stanag/ChatListener.java deleted file mode 100644 index 2fe740f68..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/ChatListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * - * 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.state.stanag; - -import com.google.gson.JsonObject; -import io.mapsmessaging.state.StateLoopProtocol; - -import java.util.function.Consumer; - -public class ChatListener implements Consumer { - - private final StateLoopProtocol protocol; - - public ChatListener(StateLoopProtocol protocol) { - this.protocol = protocol; - } - - @Override - public void accept(JsonObject jsonObject) { - System.out.println(jsonObject); - } -} diff --git a/src/main/java/io/mapsmessaging/state/stanag/NodeUpdate.java b/src/main/java/io/mapsmessaging/state/stanag/NodeUpdate.java deleted file mode 100644 index 89190d469..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/NodeUpdate.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * - * 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.state.stanag; - -import com.google.gson.Gson; -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.api.Destination; -import io.mapsmessaging.api.MessageBuilder; -import io.mapsmessaging.api.Session; -import io.mapsmessaging.api.features.DestinationType; -import io.mapsmessaging.api.features.QualityOfService; -import io.mapsmessaging.engine.schema.SchemaManager; -import io.mapsmessaging.security.uuid.NamedVersions; -import io.mapsmessaging.state.GsonStanagHelper; -import io.mapsmessaging.state.config.StanagConfig; -import io.mapsmessaging.state.drone.core.EntityTwin; -import io.mapsmessaging.state.drone.core.TwinObserver; -import io.mapsmessaging.state.drone.core.TwinUpdateContext; -import io.mapsmessaging.state.drone.core.UuidFactory; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.drone.model.Contact; -import io.mapsmessaging.state.drone.util.UuidGenerator; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.node.common.NodeMessageSupport; -import io.mapsmessaging.state.stanag.messages.node.description.NodeDescriptionBuilder; -import io.mapsmessaging.state.stanag.messages.node.dynamic.DynamicUpdate; -import io.mapsmessaging.state.stanag.messages.node.dynamic.DynamicUpdateBuilder; -import io.mapsmessaging.state.stanag.messages.node.status.NodeStatusBuilder; -import lombok.Getter; -import lombok.Setter; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Clock; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class NodeUpdate implements TwinObserver { - - private final Session session; - private final Map statusMap; - private final long descriptionInterval; - private final long statusInterval; - - private final String topicTemplate; - private final NodeDescriptionBuilder nodeDescriptionBuilder; - private final NodeStatusBuilder nodeStatusBuilder; - private final DynamicUpdateBuilder dynamicUpdateBuilder; - private final Gson gson; - - public NodeUpdate(Session session, StanagConfig stanagConfig) { - this.session = session; - statusMap = new ConcurrentHashMap<>(); - this.descriptionInterval = stanagConfig.getDescriptionIntervalSec() * 1000L; - this.statusInterval = stanagConfig.getStatusIntervalSec() * 1000L; - this.topicTemplate = stanagConfig.getTaskTopicTemplate(); - MessageHeaderBuilder messageHeaderBuilder = new MessageHeaderBuilder(Clock.systemUTC()); - NodeMessageSupport nodeMessageSupport = new NodeMessageSupport(); - nodeStatusBuilder = new NodeStatusBuilder(messageHeaderBuilder, nodeMessageSupport); - nodeDescriptionBuilder = new NodeDescriptionBuilder(messageHeaderBuilder, nodeMessageSupport); - dynamicUpdateBuilder = new DynamicUpdateBuilder(messageHeaderBuilder, nodeMessageSupport); - gson = GsonStanagHelper.createGson(); - } - - @Override - public void onTwinAdded(EntityTwin twin, TwinUpdateContext context) { - TwinStatus status = statusMap.get(twin.getTwinId()); - if(status == null) { - statusMap.put(twin.getTwinId(), new TwinStatus()); - } - else{ - status.setLastStatusUpdate(System.currentTimeMillis()); - status.setLastDescriptionUpdate(0); - } - } - - @Override - public void onTwinUpdated(String twinId, EntityTwin current, TwinUpdateContext context) { - TwinStatus status = statusMap.get(twinId); - long currentTime = System.currentTimeMillis(); - if(status != null) { - if(currentTime - status.getLastDescriptionUpdate() > descriptionInterval) { - if(current.getGeoPosition() != null && current.getGeoPosition().getLatitude() != null && current.getGeoPosition().getLongitude() != null) { - sendDescription(status, current); - status.setLastDescriptionUpdate(currentTime); - status.setSentDescription(true); - } - } - else if(currentTime - status.getLastStatusUpdate() > statusInterval && status.isSentDescription()) { - sendStatus(status, current); - status.setLastStatusUpdate(currentTime); - List contactList = ((DroneTwin)current).getContactList(); - if(contactList != null && !contactList.isEmpty()) { - sendContacts((DroneTwin)current, status, contactList); - } - } - } - else{ - onTwinAdded(current, context); - } - } - - private void sendContacts(DroneTwin droneTwin, TwinStatus status, List contactList) { - for (Contact contact : contactList) { - Optional dynamicUpdate = dynamicUpdateBuilder.build(droneTwin, contact); - if (dynamicUpdate.isPresent()) { - String json = gson.toJson(dynamicUpdate.get()); - String topicName = computeTopic(droneTwin.getUuid().toString(), "MessageTypeEnum_DYNAMIC_UPDATE"); - publish(status, topicName, json); - } - } - } - - private void sendStatus(TwinStatus status, EntityTwin current) { - String jsonStatus = gson.toJsonTree(nodeStatusBuilder.build((DroneTwin) current)).getAsJsonObject().toString(); - String topic = computeTopic(current.getUuid().toString(), "MessageTypeEnum_NODE_STATUS"); - publish(status, topic, jsonStatus); - } - - private void sendDescription(TwinStatus status, EntityTwin current) { - String jsonStatus = gson.toJsonTree(nodeDescriptionBuilder.build((DroneTwin) current)).getAsJsonObject().toString(); - String topic = computeTopic(current.getUuid().toString(), "MessageTypeEnum_NODE_DESCRIPTION"); - publish(status, topic, jsonStatus); - } - - private void publish(TwinStatus status, String topic, String json) { - try { - Destination destination = status.destinationMap.get(topic); - if(destination == null){ - destination = findDestination(topic); - status.destinationMap.put(topic, destination); - } - MessageBuilder messageBuilder = new MessageBuilder(); - messageBuilder.setOpaqueData(json.getBytes(StandardCharsets.UTF_8)) - .setQoS(QualityOfService.AT_MOST_ONCE) - .setSchemaId(SchemaManager.DEFAULT_JSON_SCHEMA.toString()); - destination.storeMessage(messageBuilder.build()); - } catch (IOException e) { - // log this - } - } - - - private Destination findDestination(String topicName) throws IOException{ - try { - return session.findDestination(topicName, DestinationType.TOPIC).get(30, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException(e); - } - } - - @Override - public void onTwinRemoved(EntityTwin removed, TwinUpdateContext context) { - TwinStatus status = statusMap.remove(removed.getTwinId()); - status.close(); - } - - - private String computeTopic(String twinId, String messageEnum) { - String topic = topicTemplate.replace("{twinId}", twinId); - topic = topic.replace("{messageEnumName}", messageEnum); - return topic; - } - - @Getter - @Setter - private static class TwinStatus { - private long lastStatusUpdate = System.currentTimeMillis(); - private long lastDescriptionUpdate = 0; - private String topicPath = "topic"; - private boolean sentDescription = false; - private Map destinationMap = new ConcurrentHashMap<>(); - - public void close(){ - destinationMap.clear(); - } - - } -} diff --git a/src/main/java/io/mapsmessaging/state/stanag/StanagSession.java b/src/main/java/io/mapsmessaging/state/stanag/StanagSession.java deleted file mode 100644 index 8a3eb2fcc..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/StanagSession.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * - * 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.state.stanag; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.mapsmessaging.api.Destination; -import io.mapsmessaging.api.MessageEvent; -import io.mapsmessaging.api.features.DestinationType; -import io.mapsmessaging.api.features.QualityOfService; -import io.mapsmessaging.api.message.Message; -import io.mapsmessaging.network.io.impl.noop.NoOpEndPoint; -import io.mapsmessaging.state.MessageHandler; -import io.mapsmessaging.state.StateLoopProtocol; -import io.mapsmessaging.state.config.StanagConfig; -import io.mapsmessaging.state.drone.core.TwinManager; -import io.mapsmessaging.state.stanag.audit.Auditor; -import io.mapsmessaging.utilities.Lifecycle; -import lombok.Getter; -import lombok.NonNull; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; - -public class StanagSession implements MessageHandler, TaskMessageSender, Lifecycle { - - private final TwinManager twinManager; - - private final StateLoopProtocol protocol; - private final String taskTopic; - private final String chatTopic; - private final String taskTopicTemplate; - private final TaskListener taskListener; - private final Consumer chatListener; - private final StanagConfig stanagConfig; - private final Map destinationCache = new ConcurrentHashMap<>(); - - private NodeUpdate nodeUpdateListener; - - @Getter - private final Auditor auditor; - - public StanagSession(@NonNull @NotNull TwinManager twinManager, @NonNull @NotNull StanagConfig stanagConfig) { - this.twinManager = twinManager; - this.stanagConfig = stanagConfig; - this.protocol = new StateLoopProtocol(new NoOpEndPoint(1, null, new ArrayList<>()), this); - this.auditor = twinManager.getAuditor(); - this.taskTopic = stanagConfig.getTaskTopic(); - this.chatTopic = stanagConfig.getChatTopic(); - this.taskTopicTemplate = stanagConfig.getTaskTopicTemplate(); - this.taskListener = new TaskListener(twinManager, this, stanagConfig); - this.chatListener = new ChatListener(protocol); - } - - public void start() { - try { - if (!isConfigured(taskTopic) && !isConfigured(chatTopic)) { - return; - } - protocol.connect(UUID.randomUUID().toString(), "anonymous", "anonymous"); - this.nodeUpdateListener = new NodeUpdate(protocol.getSession(), stanagConfig); - if (isConfigured(taskTopic)) { - subscribe(taskTopic); - } - if (isConfigured(chatTopic) && !chatTopic.equals(taskTopic)) { - subscribe(chatTopic); - } - taskListener.start(); - } catch (IOException e) { - // Log this - } - twinManager.addObserver(nodeUpdateListener); - } - - public void stop() { - taskListener.stop(); - if (isConfigured(taskTopic)) { - protocol.unsubscribeLocal(taskTopic); - } - - if (isConfigured(chatTopic) && !chatTopic.equals(taskTopic)) { - protocol.unsubscribeLocal(chatTopic); - } - - try { - protocol.close(); - } catch (IOException e) { - // log this - } - } - - @Override - public void handle(@NonNull @NotNull MessageEvent messageEvent) { - try { - JsonObject jsonObject = parseJson(messageEvent.getMessage()); - if (jsonObject == null) { - return; - } - String destinationName = messageEvent.getDestinationName(); - if (matchesTopic(taskTopic, destinationName)) { - taskListener.accept(jsonObject); - } - - if (matchesTopic(chatTopic, destinationName)) { - chatListener.accept(jsonObject); - } - } finally { - messageEvent.getCompletionTask().run(); - } - } - - @Override - public void sendTaskMessage(String taskTopic, Message message) { - respond(taskTopic, message); - } - - public void respond(String topicName, Message message) { - Destination destination = resolveDestination(topicName); - - if (destination == null) { - return; - } - - try { - destination.storeMessage(message); - } catch (IOException exception) { - // log this - } - } - - private Destination resolveDestination(String topicName) { - destinationCache.computeIfPresent(topicName, (cachedTopicName, destination) -> destination.isClosed() ? null : destination); - return destinationCache.computeIfAbsent(topicName, this::findDestination); - } - - private Destination findDestination(String topicName) { - try { - return protocol.getSession().findDestination(topicName, DestinationType.TOPIC).get(1, TimeUnit.SECONDS); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return null; - } catch (ExecutionException | TimeoutException exception) { - // log this - return null; - } - } - - private void subscribe(String topicName) throws IOException { - protocol.subscribeLocal(topicName, topicName, QualityOfService.AT_MOST_ONCE, null, null, null, null, null); - } - - private JsonObject parseJson(Message message) { - byte[] opaqueData = message.getOpaqueData(); - - if (opaqueData == null || opaqueData.length == 0) { - return null; - } - - try { - return JsonParser.parseString(new String(opaqueData, StandardCharsets.UTF_8)).getAsJsonObject(); - } catch (RuntimeException runtimeException) { - return null; - } - } - - private boolean matchesTopic(String configuredTopic, String destinationName) { - if (!isConfigured(configuredTopic) || destinationName == null) { - return false; - } - - return configuredTopic.equals(destinationName) || matchesWildcardTopic(configuredTopic, destinationName); - } - - private boolean matchesWildcardTopic(String configuredTopic, String destinationName) { - String[] configuredParts = configuredTopic.split("/"); - String[] destinationParts = destinationName.split("/"); - - int configuredIndex = 0; - int destinationIndex = 0; - - while (configuredIndex < configuredParts.length) { - String configuredPart = configuredParts[configuredIndex]; - - if ("#".equals(configuredPart)) { - return configuredIndex == configuredParts.length - 1; - } - - if (destinationIndex >= destinationParts.length) { - return false; - } - - if (!"+".equals(configuredPart) && !configuredPart.equals(destinationParts[destinationIndex])) { - return false; - } - - configuredIndex++; - destinationIndex++; - } - - return destinationIndex == destinationParts.length; - } - - private String buildTopicName(String topicTemplate, String taskTopic) { - return (topicTemplate + "/" + taskTopic).replaceAll("/+", "/"); - } - - private boolean isConfigured(String topicName) { - return topicName != null && !topicName.isBlank(); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/StanagUuidGenerator.java b/src/main/java/io/mapsmessaging/state/stanag/StanagUuidGenerator.java deleted file mode 100644 index c6e077b48..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/StanagUuidGenerator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * 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.state.stanag; - - -import io.mapsmessaging.security.uuid.NamedVersions; -import io.mapsmessaging.state.drone.util.UuidGenerator; - -import java.security.NoSuchAlgorithmException; -import java.util.Locale; -import java.util.UUID; - -public class StanagUuidGenerator extends UuidGenerator { - - @Override - public UUID generateUuid(String namespace) { - try { - return resolvePlatformUuid("EE", UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"), "stickleback", namespace); - } catch (NoSuchAlgorithmException e) { - return super.generateUuid(namespace); - } - } - - - private UUID resolvePlatformUuid(String countryCode2Char, UUID suppliedUuid, String platformName, String platformId) throws NoSuchAlgorithmException { - String uuidSource = ("urn:nato:stanag:4817:"+countryCode2Char+":" + platformName + ":" + platformId).toUpperCase(Locale.ROOT); - return io.mapsmessaging.security.uuid.UuidGenerator.getInstance().generate(NamedVersions.SHA1, suppliedUuid, uuidSource ); - } -} diff --git a/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommand.java b/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommand.java deleted file mode 100644 index 362f1bd58..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommand.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * - * 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.state.stanag; - -import com.google.gson.JsonObject; -import io.mapsmessaging.state.drone.model.GeoPosition; -import lombok.Getter; -import lombok.ToString; - -import java.util.UUID; - -@Getter -@ToString -public class TaskAdminCommand { - - private static final String TASK_ADMIN_ACTION_PREFIX = "TaskAdminActionEnum_"; - private static final String TASK_TYPE_PREFIX = "TaskTypeEnum_"; - - private final String action; - private final String taskType; - private final String identifier; - private final UUID taskId; - private final UUID nodeIdentifier; - private final UUID authorityGuid; - private final String positionIdentifier; - private final GeoPosition position; - - public TaskAdminCommand( - String action, - String taskType, - String identifier, - String nodeIdentifier, - String authorityGuid, - String positionIdentifier, - GeoPosition position) { - this.action = action; - this.taskType = taskType; - this.identifier = identifier; - this.taskId = UUID.fromString(identifier); - this.nodeIdentifier = UUID.fromString(nodeIdentifier); - this.authorityGuid = UUID.fromString(authorityGuid); - this.positionIdentifier = positionIdentifier; - this.position = position; - } - - public static TaskAdminCommand fromJson(JsonObject jsonObject) throws TaskAdminCommandException { - try { - JsonObject bodyObject = getRequiredObject(jsonObject, "body"); - - String action = stripPrefix(getRequiredString(bodyObject, "action"), TASK_ADMIN_ACTION_PREFIX); - - String identifier = getRequiredString(bodyObject, "identifier"); - String nodeIdentifier = getRequiredString(bodyObject, "node"); - - JsonObject authorityObject = getRequiredObject(bodyObject, "authority"); - validateDiscriminator(authorityObject, "AuthorityTypeEnum_GUID"); - String authorityGuid = getRequiredString(authorityObject, "guid"); - - JsonObject descriptionObject = getRequiredObject(bodyObject, "description"); - - String taskType = stripPrefix( - getRequiredString(descriptionObject, "$discriminator"), - TASK_TYPE_PREFIX - ); - - if (!"REPOSITION".equals(taskType)) { - throw new TaskAdminCommandException("Unsupported task type: " + taskType); - } - - JsonObject repositionObject = getRequiredObject(descriptionObject, "reposition"); - JsonObject locationObject = getRequiredObject(repositionObject, "location"); - - String positionIdentifier = getRequiredString(locationObject, "identifier"); - - JsonObject geometryObject = getRequiredObject(locationObject, "location"); - validateDiscriminator(geometryObject, "GeometryTypeEnum_POINT"); - - JsonObject pointObject = getRequiredObject(geometryObject, "point"); - - GeoPosition position = new GeoPosition(); - position.setLatitude(getRequiredDouble(pointObject, "latitude")); - position.setLongitude(getRequiredDouble(pointObject, "longitude")); - position.setAltitudeMslMeters(getRequiredDouble(pointObject, "altitude")); - - return new TaskAdminCommand(action, taskType, identifier, nodeIdentifier, authorityGuid, positionIdentifier, position); - } catch (IllegalStateException | ClassCastException exception) { - throw new TaskAdminCommandException("Invalid TASK_ADMIN command structure", exception); - } - } - - private static String stripPrefix(String value, String prefix) { - if (value != null && value.startsWith(prefix)) { - return value.substring(prefix.length()); - } - return value; - } - - private static void validateDiscriminator(JsonObject jsonObject, String expectedValue) - throws TaskAdminCommandException { - String actualValue = getRequiredString(jsonObject, "$discriminator"); - if (!expectedValue.equals(actualValue)) { - throw new TaskAdminCommandException( - "Unsupported discriminator: " + actualValue + ", expected: " + expectedValue); - } - } - - private static JsonObject getRequiredObject(JsonObject jsonObject, String name) - throws TaskAdminCommandException { - if (jsonObject == null || !jsonObject.has(name) || jsonObject.get(name).isJsonNull()) { - throw new TaskAdminCommandException("Missing required object: " + name); - } - if (!jsonObject.get(name).isJsonObject()) { - throw new TaskAdminCommandException("Required field is not an object: " + name); - } - return jsonObject.getAsJsonObject(name); - } - - private static String getRequiredString(JsonObject jsonObject, String name) - throws TaskAdminCommandException { - if (jsonObject == null || !jsonObject.has(name) || jsonObject.get(name).isJsonNull()) { - throw new TaskAdminCommandException("Missing required field: " + name); - } - return jsonObject.get(name).getAsString(); - } - - private static Double getRequiredDouble(JsonObject jsonObject, String name) - throws TaskAdminCommandException { - if (jsonObject == null || !jsonObject.has(name) || jsonObject.get(name).isJsonNull()) { - throw new TaskAdminCommandException("Missing required field: " + name); - } - return jsonObject.get(name).getAsDouble(); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommandException.java b/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommandException.java deleted file mode 100644 index 2d016ffe9..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/TaskAdminCommandException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * - * 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.state.stanag; - -public class TaskAdminCommandException extends Exception { - - public TaskAdminCommandException(String message) { - super(message); - } - - public TaskAdminCommandException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/TaskListener.java b/src/main/java/io/mapsmessaging/state/stanag/TaskListener.java deleted file mode 100644 index b7ae35e9a..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/TaskListener.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * - * 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.state.stanag; - -import com.google.gson.JsonObject; -import io.mapsmessaging.state.config.StanagConfig; -import io.mapsmessaging.state.config.capability.Authorities; -import io.mapsmessaging.state.drone.core.EntityTwin; -import io.mapsmessaging.state.drone.core.TwinManager; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.audit.AuditEvent; -import io.mapsmessaging.state.stanag.audit.Auditor; -import io.mapsmessaging.state.stanag.messages.*; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.task.admin.TaskAdminActionEnum; -import io.mapsmessaging.state.stanag.messages.task.admin.TaskAdminMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessageBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReasonBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessageBuilder; -import io.mapsmessaging.state.stanag.tasks.TaskDispatchResult; -import io.mapsmessaging.state.stanag.tasks.TaskDispatcher; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitorManager; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskStatusPublisher; - -import java.io.IOException; -import java.time.Clock; -import java.time.Duration; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; - -public class TaskListener implements Consumer, TaskStatusPublisher { - - private final TwinManager twinManager; - private final Auditor auditor; - private final TaskDispatcher taskDispatcher; - private final TaskResultMessageBuilder taskResultMessageBuilder; - private final TaskEventPublisher taskEventPublisher; - private final TaskMonitorManager taskMonitorManager; - private final TaskFeedbackMessageBuilder taskFeedbackMessageBuilder; - - public TaskListener(TwinManager twinManager, StanagSession protocol, StanagConfig stanagConfig) { - this.twinManager = twinManager; - this.auditor = twinManager.getAuditor(); - this.taskDispatcher = new TaskDispatcher(protocol, stanagConfig, new AtomicInteger(0)); - this.taskResultMessageBuilder = new TaskResultMessageBuilder(new MessageHeaderBuilder(Clock.systemUTC()), new ResultReasonBuilder()); - this.taskFeedbackMessageBuilder = new TaskFeedbackMessageBuilder(new MessageHeaderBuilder(Clock.systemUTC())); - this.taskEventPublisher = new TaskEventPublisher(protocol, new TaskSchemaValidator(), stanagConfig.getTaskTopicTemplate()); - this.taskMonitorManager = new TaskMonitorManager(Duration.ofSeconds(5), this); - twinManager.addObserver(taskMonitorManager); - } - - public void start() { - taskMonitorManager.start(); - } - - public void stop() { - taskMonitorManager.close(); - } - - @Override - public void publishFeedback(TaskMonitor taskMonitor, TaskFeedbackMessage taskFeedbackMessage) { - taskEventPublisher.publishFeedback(taskMonitor.getDroneUUID(), taskFeedbackMessage); - } - - @Override - public void publishResult(TaskMonitor taskMonitor, TaskResultMessage taskResultMessage) { - taskEventPublisher.publishResult(taskMonitor.getDroneUUID(), taskResultMessage); - auditTaskResult(taskMonitor, taskResultMessage); - } - - public void publishAdmin(TaskMonitor taskMonitor, TaskAdminMessage taskAdminMessage) { - taskEventPublisher.publishAdmin(taskMonitor.getDroneUUID(), taskAdminMessage); - auditTaskAdmin(taskMonitor, taskAdminMessage); - } - - @Override - public void accept(JsonObject jsonObject) { - TaskAdminCommand command = createTaskCommand(jsonObject); - if (command != null) { - if(command.getAction().equals(TaskAdminActionEnum.PUSH.name())) { - DroneTwin droneTwin = findTwin(command); - if (droneTwin == null) { - return; - } - handleTaskForTwin(droneTwin, command); - } - } else { - // log this - } - } - - private void handleTaskForTwin(DroneTwin droneTwin, TaskAdminCommand command) { - TaskDispatchResult dispatchResult = taskDispatcher.dispatch(droneTwin, command); - if (!dispatchResult.isAccepted()) { - rejectTask(dispatchResult.getDroneTwin(), command, dispatchResult.getRejectReason(), dispatchResult.getRejectText()); - } - else{ - TaskMonitor taskMonitor = dispatchResult.getTaskMonitor(); - if (taskMonitor != null) { - acceptTask(taskMonitor, command); - taskMonitorManager.add(taskMonitor); - } - } - } - - private void acceptTask(TaskMonitor taskStat, TaskAdminCommand command) { - Authorities authority = new Authorities(command.getAuthorityGuid()); - TaskStatusContext context = new TaskStatusContext(command.getTaskId(), command.getNodeIdentifier(), authority); - taskEventPublisher.publishFeedback(taskStat.getDroneUUID(), taskFeedbackMessageBuilder.build(context, TaskState.ACTIVE)); - } - - private void rejectTask(DroneTwin droneTwin, TaskAdminCommand command, ResultReason resultReason, String reasonText) { - TaskResultMessage taskResultMessage = buildRejectedTaskResult(command, resultReason, reasonText); - taskEventPublisher.publishResult(droneTwin.getUuid(), taskResultMessage); - auditRejected(droneTwin, command, resultReason, reasonText); - } - - private void rejectWithoutTwin(TaskAdminCommand command, ResultReason resultReason, String reasonText) { - TaskResultMessage taskResultMessage = buildRejectedTaskResult(command, resultReason, reasonText); - auditRejected(null, command, resultReason, reasonText); - } - - private TaskResultMessage buildRejectedTaskResult(TaskAdminCommand command, ResultReason resultReason, String reasonText) { - Authorities authority = new Authorities(command.getAuthorityGuid()); - TaskStatusContext context = new TaskStatusContext(command.getTaskId(), command.getNodeIdentifier(), authority); - return taskResultMessageBuilder.buildRejected(context, resultReason, reasonText); - } - - private void auditRejected(DroneTwin droneTwin, TaskAdminCommand command, ResultReason resultReason, String reasonText) { - if (auditor != null) { - try { - auditor.auditStanagCommandRejected(buildRejectedAuditEvent(droneTwin, command), resultReason.name() + ": " + reasonText); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - } - - private void auditTaskAdmin(TaskMonitor taskMonitor, TaskAdminMessage taskAdminMessage) { - if (auditor != null) { - AuditEvent auditEvent = taskMonitor.getAuditEvent(); - if (auditEvent == null) { - throw new IllegalStateException("Task monitor has no audit event for task " + taskMonitor.getTaskId()); - } - - try { - if (isSuccessfulTaskAdmin(taskAdminMessage)) { - auditor.auditStanagTaskResultPublished(auditEvent); - } else { - auditor.auditStanagTaskResultFailed(auditEvent, taskAdminMessage.getBody().getAction().name()); - } - } catch (IOException exception) { - exception.printStackTrace(); - } - } - } - - private void auditTaskResult(TaskMonitor taskMonitor, TaskResultMessage taskResultMessage) { - if (auditor != null) { - AuditEvent auditEvent = taskMonitor.getAuditEvent(); - if (auditEvent == null) { - throw new IllegalStateException("Task monitor has no audit event for task " + taskMonitor.getTaskId()); - } - try { - if (isSuccessfulTaskResult(taskResultMessage)) { - auditor.auditStanagTaskResultPublished(auditEvent); - } else { - auditor.auditStanagTaskResultFailed(auditEvent, taskResultMessage.getBody().getState().name()); - } - } catch (IOException exception) { - exception.printStackTrace(); - } - } - } - - private AuditEvent buildRejectedAuditEvent(DroneTwin droneTwin, TaskAdminCommand command) { - UUID nodeIdentifier = command.getNodeIdentifier(); - String droneId = nodeIdentifier.toString(); - String destination = droneTwin == null ? droneId : droneTwin.getTwinId(); - - return AuditEvent.builder() - .auditId(command.getIdentifier()) - .correlationId(command.getIdentifier()) - .parentCorrelationId("") - .actor("stanag") - .actorType("system") - .source("stanag") - .destination(destination) - .subject(droneId) - .taskId(command.getIdentifier()) - .commandId(command.getIdentifier()) - .droneId(droneId) - .stanagTaskType(command.getTaskType()) - .protocol("stanag-4817") - .build(); - } - - private boolean isSuccessfulTaskResult(TaskResultMessage taskResultMessage) { - return taskResultMessage.getBody().getState() == TaskState.SUCCEEDED; - } - - private boolean isSuccessfulTaskAdmin(TaskAdminMessage taskAdminMessage) { - return taskAdminMessage.getBody().getAction() == TaskAdminActionEnum.ASSIGN; - } - - private void publishResult(TaskResultMessage taskResultMessage) { - // Existing reject publishing behaviour preserved while you sort out the reject path. - } - - - private TaskAdminCommand createTaskCommand(JsonObject jsonObject) { - try { - return TaskAdminCommand.fromJson(jsonObject); - } catch (TaskAdminCommandException e) { - // Log this and respond - } - return null; - } - - private DroneTwin findTwin(TaskAdminCommand command) { - UUID nodeUuid; - - try { - nodeUuid = command.getNodeIdentifier(); - } catch (IllegalArgumentException exception) { - rejectWithoutTwin(command, ResultReason.LOGIC, "Invalid node identifier"); - return null; - } - - if (nodeUuid == null) { - rejectWithoutTwin(command, ResultReason.LOGIC, "Invalid node identifier"); - return null; - } - - Optional matchingTwin = - twinManager.listTwins().stream() - .filter(entityTwin -> nodeUuid.equals(entityTwin.getUuid())) - .findFirst(); - - if (matchingTwin.isEmpty()) { - rejectWithoutTwin(command, ResultReason.CAPABILITY, "No matching twin found for node"); - return null; - } - - EntityTwin entityTwin = matchingTwin.get(); - if (!(entityTwin instanceof DroneTwin droneTwin)) { - rejectWithoutTwin(command, ResultReason.CAPABILITY, "Matching twin is not a drone"); - return null; - } - - return droneTwin; - } - -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditEvent.java b/src/main/java/io/mapsmessaging/state/stanag/audit/AuditEvent.java deleted file mode 100644 index 97e3a4cdb..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditEvent.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * - * 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.state.stanag.audit; - -import lombok.*; - -import java.util.LinkedHashMap; -import java.util.Map; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AuditEvent { - - private String auditId; - private String correlationId; - private String parentCorrelationId; - - private String actor; - private String actorType; - private String source; - private String destination; - private String subject; - - private String missionId; - private String taskId; - private String commandId; - private String droneId; - - private String stanagTaskType; - private String droneCommandType; - private String protocol; - - private String targetSystem; - private String targetComponent; - - private Map attributes; - - public Map getAttributes() { - if (attributes == null) { - attributes = new LinkedHashMap<>(); - } - - return attributes; - } - - public void addAttribute(String name, String value) { - getAttributes().put(name, value); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditMessages.java b/src/main/java/io/mapsmessaging/state/stanag/audit/AuditMessages.java deleted file mode 100644 index 515d9741f..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditMessages.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * - * 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.state.stanag.audit; - -import io.mapsmessaging.logging.Category; -import io.mapsmessaging.logging.LEVEL; -import io.mapsmessaging.logging.LogMessage; -import lombok.Getter; - -public enum AuditMessages implements LogMessage { - - STANAG_COMMAND_RECEIVED(LEVEL.AUDIT, CATEGORY.C2, "Received STANAG task {} for drone {} command {}"), - STANAG_COMMAND_REJECTED(LEVEL.AUDIT, CATEGORY.C2, "Rejected STANAG task {} for drone {} reason {}"), - STANAG_COMMAND_TRANSLATED(LEVEL.AUDIT, CATEGORY.C2, "Translated STANAG task {} to drone command {} for drone {}"), - DRONE_COMMAND_DISPATCHED(LEVEL.AUDIT, CATEGORY.C2, "Dispatched drone command {} to drone {} command {}"), - DRONE_COMMAND_ACKNOWLEDGED(LEVEL.AUDIT, CATEGORY.C2, "Acknowledged drone command {} for drone {} command {}"), - DRONE_COMMAND_FAILED(LEVEL.AUDIT, CATEGORY.C2, "Failed drone command {} for drone {} reason {}"), - STANAG_TASK_RESULT_PUBLISHED(LEVEL.AUDIT, CATEGORY.C2, "Published STANAG task result {} for drone {} task {}"), - STANAG_TASK_RESULT_FAILED(LEVEL.AUDIT, CATEGORY.C2, "Failed STANAG task result {} for drone {} reason {}"); - - private final @Getter LEVEL level; - private final @Getter Category category; - private final @Getter String message; - private final @Getter int parameterCount; - - AuditMessages( - LEVEL level, - Category category, - String message - ) { - this.level = level; - this.category = category; - this.message = message; - this.parameterCount = countParameters(message); - } - - private int countParameters(String message) { - int count = 0; - int location = message.indexOf("{}"); - - while (location != -1) { - count++; - location = message.indexOf("{}", location + 2); - } - - return count; - } - - private enum CATEGORY implements Category { - - C2("Command and Control"); - - private final @Getter String description; - - CATEGORY(String description) { - this.description = description; - } - - @Override - public String getDivision() { - return "Audit"; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditPayload.java b/src/main/java/io/mapsmessaging/state/stanag/audit/AuditPayload.java deleted file mode 100644 index 91e85f9b9..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/audit/AuditPayload.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * 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.state.stanag.audit; - - -import lombok.*; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AuditPayload { - - private String name; - private String fileName; - private String contentType; - private byte[] payload; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/audit/Auditor.java b/src/main/java/io/mapsmessaging/state/stanag/audit/Auditor.java deleted file mode 100644 index 19775120b..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/audit/Auditor.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * - * 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.state.stanag.audit; - -import io.mapsmessaging.audit.*; - -import java.io.IOException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class Auditor { - - private final AuditLogger auditLogger; - private final AuditPayloadStore auditPayloadStore; - - public Auditor(AuditLogger auditLogger, AuditPayloadStore auditPayloadStore) { - this.auditLogger = auditLogger; - this.auditPayloadStore = auditPayloadStore; - } - - public AuditRecord auditStanagCommandReceived(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - return auditLogger.audit( - AuditMessages.STANAG_COMMAND_RECEIVED, - buildAuditContext(auditEvent, "stanag-command-received", AuditOutcome.SUCCESS), - writePayloads(auditEvent, payloads), - safe(auditEvent.getStanagTaskType()), - safe(auditEvent.getDroneId()), - safe(auditEvent.getCommandId())); - } - - public AuditRecord auditStanagCommandRejected( - AuditEvent auditEvent, String reason, AuditPayload... payloads) throws IOException { - return auditLogger.audit( - AuditMessages.STANAG_COMMAND_REJECTED, - buildAuditContext(auditEvent, "stanag-command-rejected", AuditOutcome.REJECTED), - writePayloads(auditEvent, payloads), - safe(auditEvent.getStanagTaskType()), - safe(auditEvent.getDroneId()), - safe(reason)); - } - - public AuditRecord auditStanagCommandTranslated(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - return auditLogger.audit( - AuditMessages.STANAG_COMMAND_TRANSLATED, - buildAuditContext(auditEvent, "stanag-command-translated", AuditOutcome.SUCCESS), - writePayloads(auditEvent, payloads), - safe(auditEvent.getStanagTaskType()), - safe(auditEvent.getDroneCommandType()), - safe(auditEvent.getDroneId())); - } - - public AuditRecord auditDroneCommandDispatched(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - return auditLogger.audit( - AuditMessages.DRONE_COMMAND_DISPATCHED, - buildAuditContext(auditEvent, "drone-command-dispatched", AuditOutcome.SUCCESS), - writePayloads(auditEvent, payloads), - safe(auditEvent.getDroneCommandType()), - safe(auditEvent.getDroneId()), - safe(auditEvent.getCommandId())); - } - - public AuditRecord auditDroneCommandAcknowledged(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - return auditLogger.audit( - AuditMessages.DRONE_COMMAND_ACKNOWLEDGED, - buildAuditContext(auditEvent, "drone-command-acknowledged", AuditOutcome.SUCCESS), - writePayloads(auditEvent, payloads), - safe(auditEvent.getDroneCommandType()), - safe(auditEvent.getDroneId()), - safe(auditEvent.getCommandId())); - } - - public AuditRecord auditDroneCommandFailed( - AuditEvent auditEvent, String reason, AuditPayload... payloads) throws IOException { - return auditLogger.audit( - AuditMessages.DRONE_COMMAND_FAILED, - buildAuditContext(auditEvent, "drone-command-failed", AuditOutcome.FAILURE), - writePayloads(auditEvent, payloads), - safe(auditEvent.getDroneCommandType()), - safe(auditEvent.getDroneId()), - safe(reason)); - } - - public AuditRecord auditStanagTaskResultPublished(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - return auditLogger.audit( - AuditMessages.STANAG_TASK_RESULT_PUBLISHED, - buildAuditContext(auditEvent, "stanag-task-result-published", AuditOutcome.SUCCESS), - writePayloads(auditEvent, payloads), - safe(auditEvent.getStanagTaskType()), - safe(auditEvent.getDroneId()), - safe(auditEvent.getTaskId())); - } - - public AuditRecord auditStanagTaskResultFailed( - AuditEvent auditEvent, String reason, AuditPayload... payloads) throws IOException { - return auditLogger.audit( - AuditMessages.STANAG_TASK_RESULT_FAILED, - buildAuditContext(auditEvent, "stanag-task-result-failed", AuditOutcome.FAILURE), - writePayloads(auditEvent, payloads), - safe(auditEvent.getStanagTaskType()), - safe(auditEvent.getDroneId()), - safe(reason)); - } - - private AuditContext buildAuditContext(AuditEvent auditEvent, String action, AuditOutcome outcome) { - AuditContext auditContext = AuditContext.builder() - .auditId(auditEvent.getAuditId()) - .correlationId(auditEvent.getCorrelationId()) - .parentCorrelationId(auditEvent.getParentCorrelationId()) - .actor(auditEvent.getActor()) - .actorType(auditEvent.getActorType()) - .source(auditEvent.getSource()) - .destination(auditEvent.getDestination()) - .subject(auditEvent.getSubject()) - .action(action) - .outcome(outcome) - .timestamp(Instant.now()) - .build(); - - addIfPresent(auditContext, "missionId", auditEvent.getMissionId()); - addIfPresent(auditContext, "taskId", auditEvent.getTaskId()); - addIfPresent(auditContext, "commandId", auditEvent.getCommandId()); - addIfPresent(auditContext, "droneId", auditEvent.getDroneId()); - addIfPresent(auditContext, "stanagTaskType", auditEvent.getStanagTaskType()); - addIfPresent(auditContext, "droneCommandType", auditEvent.getDroneCommandType()); - addIfPresent(auditContext, "protocol", auditEvent.getProtocol()); - addIfPresent(auditContext, "targetSystem", auditEvent.getTargetSystem()); - addIfPresent(auditContext, "targetComponent", auditEvent.getTargetComponent()); - - for (Map.Entry entry : auditEvent.getAttributes().entrySet()) { - addIfPresent(auditContext, entry.getKey(), entry.getValue()); - } - - return auditContext; - } - - private List writePayloads(AuditEvent auditEvent, AuditPayload... payloads) - throws IOException { - List payloadReferences = new ArrayList<>(); - - if (payloads == null) { - return payloadReferences; - } - - for (AuditPayload payload : payloads) { - if (payload == null || payload.getPayload() == null) { - continue; - } - - AuditPayloadReference payloadReference = auditPayloadStore.writePayload( - safeIdentifier(auditEvent.getCorrelationId()), - safe(payload.getName()), - safeFileName(payload.getFileName()), - payload.getPayload()); - - payloadReference.setContentType(safe(payload.getContentType())); - payloadReferences.add(payloadReference); - } - - return payloadReferences; - } - - private void addIfPresent(AuditContext auditContext, String name, String value) { - if (value == null || value.isBlank()) { - return; - } - - auditContext.addAttribute(name, value); - } - - private String safe(String value) { - if (value == null) { - return ""; - } - - return value; - } - - private String safeIdentifier(String value) { - if (value == null || value.isBlank()) { - return "unknown-correlation"; - } - - return value; - } - - private String safeFileName(String value) { - if (value == null || value.isBlank()) { - return "payload.bin"; - } - - return value.replaceAll("[^a-zA-Z0-9._-]", "_"); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskEventPublisher.java b/src/main/java/io/mapsmessaging/state/stanag/messages/TaskEventPublisher.java deleted file mode 100644 index 51aa0c1b1..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskEventPublisher.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * - * 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.state.stanag.messages; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import io.mapsmessaging.api.MessageBuilder; -import io.mapsmessaging.api.features.QualityOfService; -import io.mapsmessaging.state.GsonStanagHelper; -import io.mapsmessaging.state.stanag.StanagSession; -import io.mapsmessaging.state.stanag.messages.task.admin.TaskAdminMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; - -import java.nio.charset.StandardCharsets; -import java.util.UUID; - -public class TaskEventPublisher { - - private static final String TASK_ADMIN_SCHEMA_NAME = "catl.hibw.messages.task.TaskAdmin"; - private static final String TASK_FEEDBACK_SCHEMA_NAME = "catl.hibw.messages.task.TaskFeedback"; - private static final String TASK_RESULT_SCHEMA_NAME = "catl.hibw.messages.task.TaskResult"; - - private final StanagSession protocol; - private final Gson gson; - private final TaskSchemaValidator schemaValidator; - private final String topicTemplate; - - public TaskEventPublisher(StanagSession protocol, TaskSchemaValidator schemaValidator, String topicTemplate) { - this.protocol = protocol; - this.gson = GsonStanagHelper.createGson(); - this.schemaValidator = schemaValidator; - this.topicTemplate = topicTemplate; - } - - public void publishFeedback(UUID source, TaskFeedbackMessage taskFeedbackMessage) { - String topicPath = topicTemplate.replace("{messageEnumName}","MessageTypeEnum_TASK_FEEDBACK"); - topicPath = topicPath.replace("{twinId}",source.toString() ); - JsonObject jsonObject = gson.toJsonTree(taskFeedbackMessage).getAsJsonObject(); - schemaValidator.validate(TASK_FEEDBACK_SCHEMA_NAME, jsonObject); - publishTaskEvent(topicPath, jsonObject); - } - - public void publishResult(UUID source, TaskResultMessage taskResultMessage) { - String topicPath = topicTemplate.replace("{messageEnumName}","MessageTypeEnum_TASK_RESULT"); - topicPath = topicPath.replace("{twinId}", source.toString() ); - JsonObject jsonObject = gson.toJsonTree(taskResultMessage).getAsJsonObject(); - schemaValidator.validate(TASK_RESULT_SCHEMA_NAME, jsonObject); - publishTaskEvent(topicPath, jsonObject); - } - - public void publishAdmin(UUID source, TaskAdminMessage taskAdminMessage) { - String topicPath = topicTemplate.replace("{messageEnumName}","MessageTypeEnum_TASK_ADMIN"); - topicPath = topicPath.replace("{twinId}", source.toString() ); - JsonObject jsonObject = gson.toJsonTree(taskAdminMessage).getAsJsonObject(); - schemaValidator.validate(TASK_ADMIN_SCHEMA_NAME, jsonObject); - publishTaskEvent(topicPath, jsonObject); - } - - private void publishTaskEvent(String topicPath, JsonObject jsonObject) { - MessageBuilder messageBuilder = new MessageBuilder(); - messageBuilder - .setQoS(QualityOfService.AT_MOST_ONCE) - .setOpaqueData(jsonObject.toString().getBytes(StandardCharsets.UTF_8)); - - protocol.sendTaskMessage(topicPath, messageBuilder.build()); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskSchemaValidator.java b/src/main/java/io/mapsmessaging/state/stanag/messages/TaskSchemaValidator.java deleted file mode 100644 index c2bd71530..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskSchemaValidator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * 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.state.stanag.messages; - -import com.google.gson.JsonObject; -import io.mapsmessaging.engine.schema.SchemaManager; -import io.mapsmessaging.schemas.config.SchemaConfig; -import io.mapsmessaging.schemas.formatters.MessageFormatter; -import io.mapsmessaging.schemas.formatters.MessageFormatterFactory; -import io.mapsmessaging.schemas.formatters.ParseMode; -import io.mapsmessaging.schemas.formatters.ParsedObject; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public class TaskSchemaValidator { - - public void validate(String schemaName, JsonObject jsonObject) { - SchemaConfig schemaConfig = SchemaManager.getInstance().getSchemaByName(schemaName); - - if (schemaConfig == null) { - return; - } - - try { - MessageFormatter messageFormatter = MessageFormatterFactory.getInstance().getFormatter(schemaConfig, SchemaManager.getInstance()); - messageFormatter.parse(jsonObject.toString().getBytes(StandardCharsets.UTF_8), ParseMode.STRICT); - } catch (IOException exception) { - System.err.println("---------------------------------------------"); - System.err.println("Original:" + jsonObject); - System.err.println("---------------------------------------------"); - exception.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskState.java b/src/main/java/io/mapsmessaging/state/stanag/messages/TaskState.java deleted file mode 100644 index 033bbe89d..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskState.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * 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.state.stanag.messages; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum TaskState { - - PENDING(false), - ACTIVE(false), - RECALLING(false), - PREEMPTING(false), - PAUSING(false), - PAUSED(false), - RESUMING(false), - ON_HOLD(false), - ACTIONABLE(false), - PREPARED(false), - PLANNING(false), - WAITING_FOR_PUSH_ACK(false), - WAITING_FOR_CANCEL_ACK(false), - WAITING_FOR_PAUSE_ACK(false), - WAITING_FOR_RESUME_ACK(false), - - REJECTED(true), - RECALLED(true), - PREEMPTED(true), - ABORTED(true), - SUCCEEDED(true), - LOST(true); - - private final boolean terminal; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskStatusContext.java b/src/main/java/io/mapsmessaging/state/stanag/messages/TaskStatusContext.java deleted file mode 100644 index f726b63ce..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/TaskStatusContext.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * - * 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.state.stanag.messages; - -import io.mapsmessaging.state.config.capability.Authorities; - -import java.util.UUID; - -public record TaskStatusContext(UUID taskIdentifier, UUID nodeIdentifier, Authorities authority) { -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeader.java b/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeader.java deleted file mode 100644 index 45f00510f..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeader.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * 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.state.stanag.messages.core; - -import com.google.gson.annotations.SerializedName; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.time.Instant; - -@AllArgsConstructor -@Getter -public class MessageHeader { - @SerializedName("message_type") - private final MessageType messageType; - - private final String source; - - @SerializedName("time_sent") - private final Instant timeSent; - - private final String version; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeaderBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeaderBuilder.java deleted file mode 100644 index 85d85ceb2..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageHeaderBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * 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.state.stanag.messages.core; - -import lombok.RequiredArgsConstructor; - -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Objects; -import java.util.UUID; - -@RequiredArgsConstructor -public class MessageHeaderBuilder { - - private static final String VERSION = "0.3.0"; - - private final Clock clock; - - public MessageHeader build(MessageType messageType, UUID sourceIdentifier, Instant timestamp) { - Objects.requireNonNull(messageType, "messageType cannot be null"); - Objects.requireNonNull(sourceIdentifier, "sourceIdentifier cannot be null"); - - Instant resolvedTimestamp = timestamp != null ? timestamp : now(); - return new MessageHeader(messageType, sourceIdentifier.toString(), truncate(resolvedTimestamp), VERSION); - } - - private Instant now() { - return Instant.now(clock); - } - - private Instant truncate(Instant timestamp) { - return timestamp.truncatedTo(ChronoUnit.MILLIS); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageType.java b/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageType.java deleted file mode 100644 index e535d21f6..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/core/MessageType.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * - * 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.state.stanag.messages.core; - -import lombok.Getter; - -@Getter -public enum MessageType { - TASK_ADMIN, - NODE_STATUS, - DYNAMIC_UPDATE, - CHAT, - TASK_FEEDBACK, - NODE_DESCRIPTION, - TASK_RESULT -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EntityDescription.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EntityDescription.java deleted file mode 100644 index 3988c792c..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EntityDescription.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class EntityDescription { - - private final String version; - - private final String name; - - private final String description; - - @SerializedName("context_type") - private final String contextType; - - @SerializedName("standard_identity") - private final String standardIdentity; - - @SerializedName("symbol_set") - private final String symbolSet; - - private final String status; - - private final String entity; - - @SerializedName("entity_type") - private final String entityType; - - @SerializedName("entity_subtype") - private final String entitySubtype; - - @SerializedName("sector_1") - private final String sector1; - - @SerializedName("sector_2") - private final String sector2; - - @SerializedName("headquarters_task_force_dummy") - private final String headquartersTaskForceDummy; - - @SerializedName("unit_echelon_equipment_mobility") - private final String unitEchelonEquipmentMobility; - - private final String organization; - - private final String nationality; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EulerAngles.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EulerAngles.java deleted file mode 100644 index 96d8a37e5..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/EulerAngles.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class EulerAngles { - - private final Double roll; - - private final Double pitch; - - private final Double yaw; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/LatitudeLongitudeAltitude.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/LatitudeLongitudeAltitude.java deleted file mode 100644 index cce839450..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/LatitudeLongitudeAltitude.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import lombok.Builder; -import lombok.Getter; - -import java.util.List; - -@Getter -@Builder -public class LatitudeLongitudeAltitude { - - private final Double latitude; - - private final Double longitude; - - private final List altitude; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/NodeMessageSupport.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/NodeMessageSupport.java deleted file mode 100644 index 49a4f794b..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/NodeMessageSupport.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.drone.model.GeoPosition; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class NodeMessageSupport { - - private static final String POSITION_TYPE_LATITUDE_LONGITUDE_ALTITUDE = "PositionTypeEnum_LATITUDE_LONGITUDE_ALTITUDE"; - private static final String ALTITUDE_TYPE_WGS = "AltitudeTypeEnum_WGS"; - private static final String ORIENTATION_TYPE_EULER_ANGLES = "OrientationTypeEnum_EULER_ANGLES"; - private static final String VELOCITY_TYPE_SPEED_COURSE_CLIMB_RATE = "VelocityTypeEnum_SPEED_COURSE_CLIMB_RATE"; - - public Map buildDescription(DroneTwin droneTwin) { - Map description = new HashMap<>(); - - if (droneTwin.getDescription() != null) { - description.putAll(droneTwin.getDescription()); - } - - if (droneTwin.getCallSign() != null) { - description.put("name", droneTwin.getCallSign()); - } - - return description; - } - - public Pose buildPose(DroneTwin droneTwin) { - return Pose.builder() - .position(buildPosition(droneTwin.getGeoPosition())) - .orientation(buildOrientation(droneTwin.getOrientation())) - .build(); - } - - public Velocity buildVelocity(DroneTwin droneTwin) { - return Velocity.builder() - .discriminator(VELOCITY_TYPE_SPEED_COURSE_CLIMB_RATE) - .speedCourseClimbRate(buildSpeedCourseClimbRate(droneTwin)) - .build(); - } - - private Position buildPosition(GeoPosition geoPosition) { - return Position.builder() - .discriminator(POSITION_TYPE_LATITUDE_LONGITUDE_ALTITUDE) - .latitudeLongitudeAltitude(buildLatitudeLongitudeAltitude(geoPosition)) - .build(); - } - - private LatitudeLongitudeAltitude buildLatitudeLongitudeAltitude(GeoPosition geoPosition) { - if (geoPosition == null) { - return LatitudeLongitudeAltitude.builder().build(); - } - - return LatitudeLongitudeAltitude.builder() - .latitude(geoPosition.getLatitude()) - .longitude(geoPosition.getLongitude()) - .altitude(buildAltitude(geoPosition)) - .build(); - } - - private List buildAltitude(GeoPosition geoPosition) { - if (geoPosition.getAltitudeMslMeters() == null) { - return null; - } - - Altitude altitude = Altitude.builder() - .value(geoPosition.getAltitudeMslMeters()) - .type(ALTITUDE_TYPE_WGS) - .build(); - - return List.of(altitude); - } - - private Orientation buildOrientation(io.mapsmessaging.state.drone.model.Orientation orientation) { - return Orientation.builder() - .discriminator(ORIENTATION_TYPE_EULER_ANGLES) - .eulerAngles(buildEulerAngles(orientation)) - .build(); - } - - private EulerAngles buildEulerAngles(io.mapsmessaging.state.drone.model.Orientation orientation) { - if (orientation == null) { - return EulerAngles.builder().build(); - } - - return EulerAngles.builder() - .roll(orientation.getRollDegrees()) - .pitch(orientation.getPitchDegrees()) - .yaw(orientation.getYawDegrees()) - .build(); - } - - private SpeedCourseClimbRate buildSpeedCourseClimbRate(DroneTwin droneTwin) { - return SpeedCourseClimbRate.builder() - .speed(droneTwin.getGroundSpeedMetersPerSecond()) - .course(droneTwin.getHeadingDegrees()) - .climbRate(normaliseZero(droneTwin.getVerticalSpeedMetersPerSecond())) - .build(); - } - - private Double normaliseZero(Double value) { - if (value == null) { - return null; - } - - if (Double.compare(value, -0.0d) == 0 || Math.abs(value) < 0.0000001d) { - return 0.0d; - } - - return value; - } - - public Pose buildPose(GeoPosition geoPosition) { - Position position = buildPosition(geoPosition); - - if (position == null) { - return null; - } - - return Pose.builder() - .position(position) - .build(); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Orientation.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Orientation.java deleted file mode 100644 index a1f317a0f..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Orientation.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Orientation { - - @SerializedName("$discriminator") - private final String discriminator; - - @SerializedName("euler_angles") - private final EulerAngles eulerAngles; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Pose.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Pose.java deleted file mode 100644 index 747ff2400..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Pose.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Pose { - - private final Position position; - - private final Orientation orientation; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Position.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Position.java deleted file mode 100644 index 896c923a0..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Position.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Position { - - @SerializedName("$discriminator") - private final String discriminator; - - @SerializedName("latitude_longitude_altitude") - private final LatitudeLongitudeAltitude latitudeLongitudeAltitude; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/SpeedCourseClimbRate.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/SpeedCourseClimbRate.java deleted file mode 100644 index ae848863a..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/SpeedCourseClimbRate.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class SpeedCourseClimbRate { - - private final Double speed; - - private final Double course; - - @SerializedName("climb_rate") - private final Double climbRate; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Velocity.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Velocity.java deleted file mode 100644 index 25c099e30..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/common/Velocity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.common; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Velocity { - - @SerializedName("$discriminator") - private final String discriminator; - - @SerializedName("speed_course_climb_rate") - private final SpeedCourseClimbRate speedCourseClimbRate; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescription.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescription.java deleted file mode 100644 index 4f97b8ea1..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescription.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.description; - - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class NodeDescription { - - private final MessageHeader header; - - private final NodeDescriptionBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBody.java deleted file mode 100644 index 5029ba346..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBody.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.description; - -import com.google.gson.annotations.SerializedName; -import io.mapsmessaging.state.config.capability.TaskCapabilities; -import io.mapsmessaging.state.stanag.messages.node.common.Pose; -import io.mapsmessaging.state.stanag.messages.node.common.Velocity; -import lombok.Builder; -import lombok.Getter; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -@Getter -@Builder -public class NodeDescriptionBody { - - private final UUID identifier; - - private final Map description; - - private final TaskCapabilities capabilities; - - private final Instant timestamp; - - @SerializedName("time_of_validity") - private final Instant timeOfValidity; - - private final Pose pose; - - private final Velocity velocity; - - @SerializedName("time_of_initiation") - private final Instant timeOfInitiation; - - @SerializedName("source_of_information") - private final UUID sourceOfInformation; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBuilder.java deleted file mode 100644 index 30b118c97..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/description/NodeDescriptionBuilder.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.description; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import io.mapsmessaging.state.stanag.messages.node.common.NodeMessageSupport; -import lombok.RequiredArgsConstructor; - -import java.util.Objects; - -@RequiredArgsConstructor -public class NodeDescriptionBuilder { - - private final MessageHeaderBuilder messageHeaderBuilder; - - private final NodeMessageSupport nodeMessageSupport; - - public NodeDescription build(DroneTwin droneTwin) { - Objects.requireNonNull(droneTwin, "droneTwin cannot be null"); - Objects.requireNonNull(droneTwin.getUuid(), "droneTwin uuid cannot be null"); - - MessageHeader header = messageHeaderBuilder.build( - MessageType.NODE_DESCRIPTION, - droneTwin.getUuid(), - droneTwin.getLastSeenAt()); - - NodeDescriptionBody body = NodeDescriptionBody.builder() - .identifier(droneTwin.getUuid()) - .description(nodeMessageSupport.buildDescription(droneTwin)) - .capabilities(droneTwin.getCapabilities()) - .timestamp(droneTwin.getOperationalUpdatedAt()) - .timeOfValidity(droneTwin.getValidTill()) - .pose(nodeMessageSupport.buildPose(droneTwin)) - .velocity(nodeMessageSupport.buildVelocity(droneTwin)) - .timeOfInitiation(droneTwin.getCreatedAt()) - .build(); - - return new NodeDescription(header, body); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdate.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdate.java deleted file mode 100644 index 03920a5ab..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdate.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class DynamicUpdate { - - private final MessageHeader header; - - private final DynamicUpdateBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBody.java deleted file mode 100644 index d490c2aff..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBody.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class DynamicUpdateBody { - - private final DynamicUpdateOperation operation; - -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBuilder.java deleted file mode 100644 index a40aa05e4..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateBuilder.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.drone.model.Contact; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import io.mapsmessaging.state.stanag.messages.node.common.EntityDescription; -import io.mapsmessaging.state.stanag.messages.node.common.NodeMessageSupport; -import io.mapsmessaging.state.stanag.messages.node.common.Pose; -import lombok.RequiredArgsConstructor; - -import java.time.Instant; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; - -@RequiredArgsConstructor -public class DynamicUpdateBuilder { - - private static final String OPERATION_TYPE_PUT_VALUE = "DynamicUpdateOperationTypeEnum_PUT_VALUE"; - - private static final String VALUE_TYPE_TRACK = "ValueTypeEnum_TRACK"; - - private static final String CONTEXT_TYPE_SIMULATION = "ContextEnum_SIMULATION"; - - private static final String STANDARD_IDENTITY_FRIEND = "StandardIdentityEnum_FRIEND"; - - private static final String SYMBOL_SET_SEA_SURFACE = "SymbolSetEnum_SEA_SURFACE"; - - private static final String ENTITY_STATUS_PRESENT = "EntityStatusEnum_PRESENT"; - - private static final String ORGANIZATION_MAPS = "MAPS"; - - private static final String NATIONALITY_EST = "EST"; - - private static final String ENTITY_VERSION = "10"; - - private static final String DEFAULT_ENTITY = "11"; - - private static final String DEFAULT_ENTITY_TYPE = "11"; - - private static final String DEFAULT_ENTITY_SUBTYPE = "00"; - - private static final String DEFAULT_SECTOR_1 = "00"; - - private static final String DEFAULT_SECTOR_2 = "00"; - - private static final String HEADQUARTERS_TASK_FORCE_DUMMY_NOT_APPLICABLE = "HeadquartersTaskForceDummyEnum_NOT_APPLICABLE"; - - private static final String UNIT_ECHELON_EQUIPMENT_MOBILITY_UNKNOWN = "UnitEchelonEquipmentMobilityEnum_UNKNOWN"; - - private static final String TRACK_PHASE_TRACKED = "TrackPhase_TRACKED"; - - private final MessageHeaderBuilder messageHeaderBuilder; - - private final NodeMessageSupport nodeMessageSupport; - - public Optional build(DroneTwin droneTwin, Contact contact) { - Objects.requireNonNull(droneTwin, "droneTwin cannot be null"); - Objects.requireNonNull(contact, "contact cannot be null"); - Objects.requireNonNull(droneTwin.getUuid(), "droneTwin uuid cannot be null"); - - if (contact.getId() == null || contact.getUpdatedTimeMs() <= 0) { - return Optional.empty(); - } - - Pose pose = nodeMessageSupport.buildPose(contact.getPosition()); - - if (pose == null) { - return Optional.empty(); - } - - MessageHeader header = messageHeaderBuilder.build( - MessageType.DYNAMIC_UPDATE, - droneTwin.getUuid(), - droneTwin.getLastSeenAt()); - - Track track = Track.builder() - .identifier(contact.getId()) - .description(buildEntityDescription(contact)) - .timestamp(Instant.ofEpochMilli(contact.getUpdatedTimeMs())) - .pose(pose) - .timeOfInitiation(buildCreatedTimestamp(contact)) - .timeOfValidity(buildValidityTimestamp(contact)) - .trackPhase(TRACK_PHASE_TRACKED) - .build(); - - PutValue putValue = PutValue.builder() - .discriminator(VALUE_TYPE_TRACK) - .track(track) - .build(); - - DynamicUpdateOperation operation = DynamicUpdateOperation.builder() - .discriminator(OPERATION_TYPE_PUT_VALUE) - .putValue(putValue) - .build(); - - DynamicUpdateBody body = DynamicUpdateBody.builder() - .operation(operation) - .build(); - - return Optional.of(new DynamicUpdate(header, body)); - } - - private EntityDescription buildEntityDescription(Contact contact) { - if (contact.getDescription() == null || contact.getDescription().isBlank()) { - return null; - } - - return EntityDescription.builder() - .version(ENTITY_VERSION) - .name(contact.getDescription()) - .contextType(CONTEXT_TYPE_SIMULATION) - .standardIdentity(STANDARD_IDENTITY_FRIEND) - .symbolSet(SYMBOL_SET_SEA_SURFACE) - .status(ENTITY_STATUS_PRESENT) - .entity(DEFAULT_ENTITY) - .entityType(DEFAULT_ENTITY_TYPE) - .entitySubtype(DEFAULT_ENTITY_SUBTYPE) - .sector1(DEFAULT_SECTOR_1) - .sector2(DEFAULT_SECTOR_2) - .headquartersTaskForceDummy(HEADQUARTERS_TASK_FORCE_DUMMY_NOT_APPLICABLE) - .unitEchelonEquipmentMobility(UNIT_ECHELON_EQUIPMENT_MOBILITY_UNKNOWN) - .organization(ORGANIZATION_MAPS) - .nationality(NATIONALITY_EST) - .build(); - } - - private Instant buildCreatedTimestamp(Contact contact) { - if (contact.getCreatedTimeMs() <= 0) { - return null; - } - - return Instant.ofEpochMilli(contact.getCreatedTimeMs()); - } - - private Instant buildValidityTimestamp(Contact contact) { - if (contact.getUpdatedTimeMs() <= 0 || contact.getTtlMillis() <= 0) { - return null; - } - - return Instant.ofEpochMilli(contact.getUpdatedTimeMs() + contact.getTtlMillis()); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateOperation.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateOperation.java deleted file mode 100644 index d2044cc6e..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/DynamicUpdateOperation.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class DynamicUpdateOperation { - - @SerializedName("$discriminator") - private final String discriminator; - - @SerializedName("put_value") - private final PutValue putValue; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/PutValue.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/PutValue.java deleted file mode 100644 index f0615b61e..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/PutValue.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class PutValue { - - @SerializedName("$discriminator") - private final String discriminator; - - private final Track track; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/Track.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/Track.java deleted file mode 100644 index 2f7257558..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/dynamic/Track.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.dynamic; - -import com.google.gson.annotations.SerializedName; -import io.mapsmessaging.state.stanag.messages.node.common.EntityDescription; -import io.mapsmessaging.state.stanag.messages.node.common.Pose; -import io.mapsmessaging.state.stanag.messages.node.common.Velocity; -import lombok.Builder; -import lombok.Getter; - -import java.time.Instant; -import java.util.UUID; - -@Getter -@Builder -public class Track { - - private final UUID identifier; - - private final EntityDescription description; - - private final Instant timestamp; - - private final Pose pose; - - @SerializedName("track_phase") - private final String trackPhase; - - @SerializedName("source_of_information") - private final UUID sourceOfInformation; - - @SerializedName("time_of_initiation") - private final Instant timeOfInitiation; - - @SerializedName("time_of_validity") - private final Instant timeOfValidity; - - private final Velocity velocity; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatus.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatus.java deleted file mode 100644 index 1b5f9f577..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatus.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * - * 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.state.stanag.messages.node.status; - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class NodeStatus { - - private final MessageHeader header; - - private final NodeStatusBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBody.java deleted file mode 100644 index 0eea50aff..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBody.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.status; - -import com.google.gson.annotations.SerializedName; -import io.mapsmessaging.state.stanag.messages.node.common.Pose; -import io.mapsmessaging.state.stanag.messages.node.common.Velocity; -import lombok.Builder; -import lombok.Getter; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; - -@Getter -@Builder -public class NodeStatusBody { - - private final UUID identifier; - - private final Map description; - - private final Instant timestamp; - - @SerializedName("time_of_validity") - private final Instant timeOfValidity; - - private final Pose pose; - - private final Velocity velocity; - - @SerializedName("time_of_initiation") - private final Instant timeOfInitiation; - - @SerializedName("source_of_information") - private final UUID sourceOfInformation; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBuilder.java deleted file mode 100644 index 6205bc8b3..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/node/status/NodeStatusBuilder.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.node.status; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import io.mapsmessaging.state.stanag.messages.node.common.NodeMessageSupport; -import lombok.RequiredArgsConstructor; - -import java.util.Objects; - -@RequiredArgsConstructor -public class NodeStatusBuilder { - - private final MessageHeaderBuilder messageHeaderBuilder; - - private final NodeMessageSupport nodeMessageSupport; - - public NodeStatus build(DroneTwin droneTwin) { - Objects.requireNonNull(droneTwin, "droneTwin cannot be null"); - Objects.requireNonNull(droneTwin.getUuid(), "droneTwin uuid cannot be null"); - - MessageHeader header = messageHeaderBuilder.build( - MessageType.NODE_STATUS, - droneTwin.getUuid(), - droneTwin.getLastSeenAt()); - - NodeStatusBody body = NodeStatusBody.builder() - .identifier(droneTwin.getUuid()) - .description(nodeMessageSupport.buildDescription(droneTwin)) - .timestamp(droneTwin.getOperationalUpdatedAt()) - .timeOfValidity(droneTwin.getValidTill()) - .pose(nodeMessageSupport.buildPose(droneTwin)) - .velocity(nodeMessageSupport.buildVelocity(droneTwin)) - .timeOfInitiation(droneTwin.getCreatedAt()) - .build(); - - return new NodeStatus(header, body); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminActionEnum.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminActionEnum.java deleted file mode 100644 index c95accb65..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminActionEnum.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.admin; - -public enum TaskAdminActionEnum { - UPDATE, - PUSH, - PULL, - CANCEL, - PAUSE, - RESUME, - ASSIGN -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminBody.java deleted file mode 100644 index fb5934ab5..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminBody.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.admin; - -import io.mapsmessaging.state.config.capability.Authorities; -import lombok.Builder; -import lombok.Getter; - -import java.util.UUID; - -@Getter -@Builder -public class TaskAdminBody { - - private final UUID identifier; - - private final UUID node; - - private final TaskAdminActionEnum action; - - private final Authorities authority; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessage.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessage.java deleted file mode 100644 index 5cd0d5508..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessage.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.admin; - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class TaskAdminMessage { - - private final MessageHeader header; - - private final TaskAdminBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessageBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessageBuilder.java deleted file mode 100644 index 27c86f9cd..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/admin/TaskAdminMessageBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.admin; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.config.capability.Authorities; -import io.mapsmessaging.state.stanag.messages.TaskStatusContext; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import lombok.RequiredArgsConstructor; - -import java.time.Instant; -import java.util.Objects; - -@RequiredArgsConstructor -public class TaskAdminMessageBuilder { - - private final MessageHeaderBuilder headerBuilder; - - public TaskAdminMessage build(TaskStatusContext context, TaskAdminActionEnum action, Authorities authority) { - Objects.requireNonNull(context, "context cannot be null"); - Objects.requireNonNull(action, "action cannot be null"); - - MessageHeader header = headerBuilder.build(MessageType.TASK_ADMIN, context.nodeIdentifier() , Instant.now()); - - TaskAdminBody body = TaskAdminBody.builder() - .identifier(context.taskIdentifier()) - .node(context.nodeIdentifier()) - .action(action) - .authority(authority) - .build(); - - return new TaskAdminMessage(header, body); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackBody.java deleted file mode 100644 index 93e5ab05d..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackBody.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.feedback; - -import com.google.gson.annotations.SerializedName; -import io.mapsmessaging.state.stanag.messages.TaskState; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -import java.util.List; -import java.util.UUID; - -@Getter -@Setter -@Builder -public class TaskFeedbackBody { - - private final UUID identifier; - - private final UUID node; - - private final TaskState state; - - @SerializedName("percent_complete") - private Double percentComplete; - - @SerializedName("time_remaining") - private final String timeRemaining; - - @SerializedName("waypoints_remaining") - private final List waypointsRemaining; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessage.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessage.java deleted file mode 100644 index bd14b692e..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessage.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.feedback; - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class TaskFeedbackMessage { - - private final MessageHeader header; - - private final TaskFeedbackBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessageBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessageBuilder.java deleted file mode 100644 index 946a585a2..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/feedback/TaskFeedbackMessageBuilder.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.feedback; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.stanag.messages.TaskState; -import io.mapsmessaging.state.stanag.messages.TaskStatusContext; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import lombok.RequiredArgsConstructor; - -import java.time.Instant; -import java.util.List; -import java.util.Objects; - -@RequiredArgsConstructor -public class TaskFeedbackMessageBuilder { - - private final MessageHeaderBuilder headerBuilder; - - public TaskFeedbackMessage buildActive(TaskStatusContext context) { - return build(context, TaskState.ACTIVE); - } - - public TaskFeedbackMessage buildProgress(TaskStatusContext context, double percentComplete) { - return build(context, TaskState.ACTIVE, percentComplete, null, null); - } - - public TaskFeedbackMessage buildWaypointsRemaining( - TaskStatusContext context, - List waypointsRemaining) { - - return build(context, TaskState.ACTIVE, null, null, waypointsRemaining); - } - - public TaskFeedbackMessage build(TaskStatusContext context, TaskState taskState) { - return build(context, taskState, null, null, null); - } - - public TaskFeedbackMessage build( - TaskStatusContext context, - TaskState taskState, - Double percentComplete, - String timeRemaining, - List waypointsRemaining) { - - Objects.requireNonNull(context, "context cannot be null"); - Objects.requireNonNull(taskState, "taskState cannot be null"); - - validateFeedbackState(taskState); - validatePercentComplete(percentComplete); - - MessageHeader header = headerBuilder.build(MessageType.TASK_FEEDBACK, context.nodeIdentifier(), Instant.now()); - - TaskFeedbackBody.TaskFeedbackBodyBuilder bodyBuilder = TaskFeedbackBody.builder() - .identifier(context.taskIdentifier()) - .node(context.nodeIdentifier()) - .state(taskState); - - if (percentComplete != null) { - bodyBuilder.percentComplete(percentComplete); - } - - if (timeRemaining != null) { - bodyBuilder.timeRemaining(timeRemaining); - } - - if (waypointsRemaining != null && !waypointsRemaining.isEmpty()) { - bodyBuilder.waypointsRemaining(waypointsRemaining); - } - - return new TaskFeedbackMessage(header, bodyBuilder.build()); - } - - private void validatePercentComplete(Double percentComplete) { - if (percentComplete == null) { - return; - } - - if (percentComplete < 0 || percentComplete > 100) { - throw new IllegalArgumentException("percent_complete must be between 0 and 100"); - } - } - - private void validateFeedbackState(TaskState taskState) { - if (taskState.isTerminal()) { - throw new IllegalArgumentException("Terminal task states must use TASK_RESULT: " + taskState); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReason.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReason.java deleted file mode 100644 index 53e625ef5..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReason.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.result; - -public enum ResultReason { - SAFETY, - AUTHORITY, - ENVIRONMENT, - ENDURANCE, - LOGIC, - DYNAMICS, - HARDWARE, - COMMUNICATION, - TEMPORAL, - GEOSPATIAL, - CAPABILITY -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReasonBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReasonBuilder.java deleted file mode 100644 index 210b10a53..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/ResultReasonBuilder.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.result; - -import io.mapsmessaging.state.stanag.messages.FlexibleEnumeration; - -public class ResultReasonBuilder { - - public FlexibleEnumeration build(ResultReason resultReason, String text) { - if (resultReason == null) { - return null; - } - - if (text != null) { - return new FlexibleEnumeration(resultReason.name()+" "+text); - } - return new FlexibleEnumeration(resultReason.name()); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultBody.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultBody.java deleted file mode 100644 index c51ca0381..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultBody.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.result; - -import com.google.gson.annotations.SerializedName; -import io.mapsmessaging.state.stanag.messages.FlexibleEnumeration; -import io.mapsmessaging.state.stanag.messages.TaskState; -import lombok.Builder; -import lombok.Getter; - -import java.time.Instant; -import java.util.UUID; - -@Getter -@Builder -public class TaskResultBody { - - private final UUID identifier; - - private final UUID node; - - private final TaskState state; - - private final Instant timestamp; - - @SerializedName("result_reason") - private final FlexibleEnumeration resultReason; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessage.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessage.java deleted file mode 100644 index 444c86428..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessage.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.result; - -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class TaskResultMessage { - - private final MessageHeader header; - - private final TaskResultBody body; -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessageBuilder.java b/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessageBuilder.java deleted file mode 100644 index 4af6a9986..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/messages/task/result/TaskResultMessageBuilder.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.mapsmessaging.state.stanag.messages.task.result; - -import io.mapsmessaging.MessageDaemon; -import io.mapsmessaging.state.stanag.messages.TaskState; -import io.mapsmessaging.state.stanag.messages.TaskStatusContext; -import io.mapsmessaging.state.stanag.messages.core.MessageHeader; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.core.MessageType; -import lombok.RequiredArgsConstructor; - -import java.time.Instant; -import java.util.Objects; - -@RequiredArgsConstructor -public class TaskResultMessageBuilder { - - private final MessageHeaderBuilder headerBuilder; - - private final ResultReasonBuilder resultReasonBuilder; - - public TaskResultMessage buildSucceeded(TaskStatusContext context) { - return build(context, TaskState.SUCCEEDED, null, null); - } - - public TaskResultMessage buildRejected(TaskStatusContext context, ResultReason resultReason, String reasonText) { - return build(context, TaskState.REJECTED, resultReason, reasonText); - } - - public TaskResultMessage buildAborted(TaskStatusContext context, ResultReason resultReason,String reasonText) { - return build(context, TaskState.ABORTED, resultReason, reasonText); - } - - public TaskResultMessage buildLost(TaskStatusContext context, ResultReason resultReason,String reasonText) { - return build(context, TaskState.LOST, resultReason, reasonText); - } - - public TaskResultMessage buildRecalled(TaskStatusContext context, ResultReason resultReason,String reasonText) { - return build(context, TaskState.RECALLED, resultReason, reasonText); - } - - public TaskResultMessage buildPreempted(TaskStatusContext context, ResultReason resultReason,String reasonText) { - return build(context, TaskState.PREEMPTED, resultReason, reasonText); - } - - public TaskResultMessage build(TaskStatusContext context, TaskState taskState, ResultReason resultReason, String reasonText) { - Objects.requireNonNull(context, "context cannot be null"); - Objects.requireNonNull(taskState, "taskState cannot be null"); - - validateResultState(taskState); - - MessageHeader header = headerBuilder.build(MessageType.TASK_RESULT, context.nodeIdentifier(), Instant.now()); - TaskResultBody body = TaskResultBody.builder() - .identifier(context.taskIdentifier()) - .node(context.nodeIdentifier()) - .state(taskState) - .timestamp(Instant.now()) - .resultReason(resultReasonBuilder.build(resultReason, reasonText)) - .build(); - - return new TaskResultMessage(header, body); - } - - private void validateResultState(TaskState taskState) { - if (!taskState.isTerminal()) { - throw new IllegalArgumentException("Non-terminal task states must use TASK_FEEDBACK: " + taskState); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/RepositionTaskHandler.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/RepositionTaskHandler.java deleted file mode 100644 index eec832cb8..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/RepositionTaskHandler.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * - * 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.state.stanag.tasks; - -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.drone.model.GeoPosition; -import io.mapsmessaging.state.mavlink.messages.MavlinkCommandInt; -import io.mapsmessaging.state.stanag.StanagSession; -import io.mapsmessaging.state.stanag.TaskAdminCommand; -import io.mapsmessaging.state.stanag.audit.AuditEvent; -import io.mapsmessaging.state.stanag.tasks.monitor.RepositionTaskMonitor; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; -import io.mapsmessaging.state.util.GeoUtils; - -import java.time.Duration; -import java.util.UUID; - -public class RepositionTaskHandler extends TaskHandler { - - @Override - public TaskMonitor handle(DroneTwin droneTwin, TaskAdminCommand command, StanagSession protocol,String template, int taskSequence) { - GeoPosition newPosition = command.getPosition(); - float yaw = GeoUtils.bearingDegrees(droneTwin.getGeoPosition(), newPosition); - MavlinkCommandInt mavlinkRequest = MavlinkCommandInt.reposition(droneTwin.getSystemId(), droneTwin.getComponentId(), newPosition, yaw, taskSequence); - AuditEvent auditEvent = buildAuditEvent(droneTwin, command, mavlinkRequest, taskSequence, newPosition, yaw); - auditAndDispatch(droneTwin, protocol, mavlinkRequest, auditEvent); - return new RepositionTaskMonitor(command.getTaskId(), droneTwin, template, taskSequence, Duration.ofMinutes(60), Duration.ofSeconds(15), command.getPosition(), auditEvent); - } - - @Override - public String getTaskType() { - return "REPOSITION"; - } - - private AuditEvent buildAuditEvent( - DroneTwin droneTwin, - TaskAdminCommand command, - MavlinkCommandInt mavlinkRequest, - int taskSequence, - GeoPosition newPosition, - float yaw - ) { - String commandIdentifier = command.getIdentifier(); - String droneIdentifier = droneTwin.getUuid().toString(); - - AuditEvent auditEvent = AuditEvent.builder() - .auditId(UUID.randomUUID().toString()) - .correlationId(commandIdentifier) - .actor("stanag") - .actorType("system") - .source("stanag") - .destination("drone") - .subject(droneIdentifier) - .taskId(commandIdentifier) - .commandId(commandIdentifier) - .droneId(droneIdentifier) - .stanagTaskType(getTaskType()) - .droneCommandType(mavlinkRequest.getMessageType()) - .protocol("MAVLink") - .targetSystem(String.valueOf(droneTwin.getSystemId())) - .targetComponent(String.valueOf(droneTwin.getComponentId())) - .build(); - - auditEvent.addAttribute("taskSequence", String.valueOf(taskSequence)); - auditEvent.addAttribute("mavlinkCommand", String.valueOf(mavlinkRequest.getCommand())); - - if (newPosition.getLatitude() != null) { - auditEvent.addAttribute("latitude", String.valueOf(newPosition.getLatitude())); - } - - if (newPosition.getLongitude() != null) { - auditEvent.addAttribute("longitude", String.valueOf(newPosition.getLongitude())); - } - - if (newPosition.getAltitudeMslMeters() != null) { - auditEvent.addAttribute("altitudeMslMeters", String.valueOf(newPosition.getAltitudeMslMeters())); - } - - if (newPosition.getAltitudeAglMeters() != null) { - auditEvent.addAttribute("altitudeAglMeters", String.valueOf(newPosition.getAltitudeAglMeters())); - } - - auditEvent.addAttribute("yaw", String.valueOf(yaw)); - - return auditEvent; - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatchResult.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatchResult.java deleted file mode 100644 index ab807b708..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatchResult.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * 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.state.stanag.tasks; - -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; -import lombok.Getter; - -@Getter -public class TaskDispatchResult { - - private final DroneTwin droneTwin; - private final TaskMonitor taskMonitor; - private final ResultReason rejectReason; - private final String rejectText; - - private TaskDispatchResult(DroneTwin droneTwin, TaskMonitor taskMonitor, ResultReason rejectReason, String rejectText) { - this.droneTwin = droneTwin; - this.taskMonitor = taskMonitor; - this.rejectReason = rejectReason; - this.rejectText = rejectText; - } - - public static TaskDispatchResult accepted(DroneTwin droneTwin, TaskMonitor taskMonitor) { - return new TaskDispatchResult(droneTwin, taskMonitor, null, null); - } - - public static TaskDispatchResult rejected(DroneTwin droneTwin, ResultReason rejectReason, String rejectText) { - return new TaskDispatchResult(droneTwin, null, rejectReason, rejectText); - } - - public boolean isAccepted() { - return rejectReason == null; - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatcher.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatcher.java deleted file mode 100644 index 193712443..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskDispatcher.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * - * 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.state.stanag.tasks; - -import io.mapsmessaging.state.config.StanagConfig; -import io.mapsmessaging.state.config.capability.Authorities; -import io.mapsmessaging.state.config.capability.PlanTaskType; -import io.mapsmessaging.state.config.capability.TaskCapabilities; -import io.mapsmessaging.state.config.capability.TaskCapability; -import io.mapsmessaging.state.drone.core.EntityTwin; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.StanagSession; -import io.mapsmessaging.state.stanag.TaskAdminCommand; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; - -import java.util.Locale; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -public class TaskDispatcher { - - private final StanagSession protocol; - private final AtomicInteger taskSequence; - private final Map taskHandlers = new ConcurrentHashMap<>(); - private final StanagConfig stanagConfig; - - public TaskDispatcher(StanagSession protocol, StanagConfig stanagConfig, AtomicInteger taskSequence) { - this.protocol = protocol; - this.taskSequence = taskSequence; - this.stanagConfig = stanagConfig; - ServiceLoader.load(TaskHandler.class).forEach(this::registerTaskHandler); - } - - public TaskDispatchResult dispatch(EntityTwin twin, TaskAdminCommand command) { - if (!(twin instanceof DroneTwin droneTwin)) { - return TaskDispatchResult.rejected(null, ResultReason.CAPABILITY, "Matching twin is not a drone"); - } - - ResultReason authorisationRejectReason = getAuthorisationRejectReason(droneTwin, command); - if (authorisationRejectReason != null) { - return TaskDispatchResult.rejected(droneTwin, authorisationRejectReason, "Task is not authorised"); - } - - ResultReason validationRejectReason = getValidationRejectReason(droneTwin); - if (validationRejectReason != null) { - return TaskDispatchResult.rejected(droneTwin, validationRejectReason, "Drone is not armed"); - } - - TaskHandler taskHandler = taskHandlers.get(normaliseTaskType(command.getTaskType())); - if (taskHandler == null) { - return TaskDispatchResult.rejected(droneTwin, ResultReason.CAPABILITY, "No task handler for task type"); - } - - TaskMonitor taskMonitor = taskHandler.handle(droneTwin, command, protocol, stanagConfig.getTaskTopicTemplate(), taskSequence.getAndIncrement()); - Authorities authority = new Authorities(); - authority.setGuid(command.getAuthorityGuid()); - taskMonitor.setAuthority(authority); - taskMonitor.setAccepted(); - return TaskDispatchResult.accepted(droneTwin, taskMonitor); - } - - private void registerTaskHandler(TaskHandler taskHandler) { - String taskType = normaliseTaskType(taskHandler.getTaskType()); - TaskHandler previousTaskHandler = taskHandlers.putIfAbsent(taskType, taskHandler); - - if (previousTaskHandler != null) { - throw new IllegalStateException( - "Duplicate task handler for task type " - + taskType - + ": " - + previousTaskHandler.getClass().getName() - + " and " - + taskHandler.getClass().getName()); - } - } - - private ResultReason getAuthorisationRejectReason(DroneTwin droneTwin, TaskAdminCommand command) { - TaskCapabilities capabilities = droneTwin.getCapabilities(); - - if (capabilities == null || capabilities.getTasks() == null) { - return ResultReason.CAPABILITY; - } - - TaskCapability capability = - capabilities.getTasks().stream() - .filter(taskCapability -> matchesTaskType(taskCapability.getTaskType(), command.getTaskType())) - .findFirst() - .orElse(null); - - if (capability == null) { - return ResultReason.CAPABILITY; - } - - if (capability.getAuthorities() == null || capability.getAuthorities().length == 0) { - return null; - } - - for (Authorities authority : capability.getAuthorities()) { - if (authority.getGuid().equals(command.getAuthorityGuid())) { - return null; - } - } - - return ResultReason.AUTHORITY; - } - - private ResultReason getValidationRejectReason(DroneTwin droneTwin) { - if (!Boolean.TRUE.equals(droneTwin.getArmed())) { - return ResultReason.SAFETY; - } - - return null; - } - - private boolean matchesTaskType(PlanTaskType taskType, String taskTypeName) { - if (taskType == null) { - return false; - } - - return taskType.name().equals(taskTypeName) || taskType.toString().equals(taskTypeName); - } - - private String normaliseTaskType(String taskType) { - if (taskType == null) { - return ""; - } - - return taskType.toUpperCase(Locale.ROOT); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskHandler.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskHandler.java deleted file mode 100644 index 847442f08..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/TaskHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * - * 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.state.stanag.tasks; - -import io.mapsmessaging.api.MessageBuilder; -import io.mapsmessaging.api.features.QualityOfService; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.mavlink.messages.MavlinkCommandInt; -import io.mapsmessaging.state.stanag.StanagSession; -import io.mapsmessaging.state.stanag.TaskAdminCommand; -import io.mapsmessaging.state.stanag.audit.AuditEvent; -import io.mapsmessaging.state.stanag.audit.Auditor; -import io.mapsmessaging.state.stanag.tasks.monitor.TaskMonitor; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public abstract class TaskHandler { - - public abstract TaskMonitor handle(DroneTwin droneTwin, TaskAdminCommand command, StanagSession protocol, String template, int taskSequence); - - public abstract String getTaskType(); - - protected void auditAndDispatch(DroneTwin droneTwin, StanagSession protocol, MavlinkCommandInt mavlinkRequest, AuditEvent auditEvent) { - Auditor auditor = protocol.getAuditor(); - if (auditor != null) { - auditTranslated(auditor, auditEvent); - } - sendMavlinkRequest(droneTwin, mavlinkRequest, protocol); - if (auditor != null) { - auditDispatched(auditor, auditEvent); - } - } - - protected void sendMavlinkRequest(DroneTwin droneTwin, MavlinkCommandInt mavlinkRequest, StanagSession protocol) { - MessageBuilder messageBuilder = new MessageBuilder(); - - messageBuilder - .setOpaqueData(mavlinkRequest.toMavlinkJsonObject(255, 0).toString().getBytes(StandardCharsets.UTF_8)) - .setQoS(QualityOfService.AT_MOST_ONCE) - .setCorrelationData(droneTwin.getUniqueOutboundIdentifier()); - - protocol.respond(droneTwin.getResponseTopicName(), messageBuilder.build()); - } - - protected void auditTranslated(Auditor auditor, AuditEvent auditEvent) { - try { - auditor.auditStanagCommandTranslated(auditEvent); - } catch (IOException exception) { - throw new IllegalStateException("Unable to audit STANAG command translation", exception); - } - } - - protected void auditDispatched(Auditor auditor, AuditEvent auditEvent) { - try { - auditor.auditDroneCommandDispatched(auditEvent); - } catch (IOException exception) { - throw new IllegalStateException("Unable to audit MAVLink command dispatch", exception); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/CompletedTaskMonitor.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/CompletedTaskMonitor.java deleted file mode 100644 index 9a2e317ef..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/CompletedTaskMonitor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * - * 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.state.stanag.tasks.monitor; - -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.audit.AuditEvent; - -import java.time.Duration; -import java.util.UUID; - -public class CompletedTaskMonitor extends TaskMonitor { - - private final String taskType; - - public CompletedTaskMonitor(UUID taskId, DroneTwin droneTwin, String topicPath, int taskSequence, String taskType, AuditEvent auditEvent) { - super(taskId, droneTwin, topicPath, taskSequence, Duration.ofSeconds(1), Duration.ofSeconds(1), auditEvent); - this.taskType = taskType; - setComplete(); - } - - @Override - public String getTaskType() { - return taskType; - } - - @Override - protected void updateTask(DroneTwin droneTwin) { - setComplete(); - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/RepositionTaskMonitor.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/RepositionTaskMonitor.java deleted file mode 100644 index ab129b988..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/RepositionTaskMonitor.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * - * 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.state.stanag.tasks.monitor; - -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.drone.model.GeoPosition; -import io.mapsmessaging.state.stanag.audit.AuditEvent; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessageBuilder; -import io.mapsmessaging.state.util.GeoUtils; -import lombok.Getter; - -import java.time.Duration; -import java.time.Instant; -import java.util.UUID; - -@Getter -public class RepositionTaskMonitor extends TaskMonitor { - - public static final String TASK_TYPE = "REPOSITION"; - - private static final double COMPLETE_DISTANCE_METERS = 10.0d; - private static final double MINIMUM_CLOSING_SPEED_METERS_PER_SECOND = 0.1d; - private static final double MINIMUM_PROGRESS_PERCENTAGE_FOR_ETA = 1.0d; - private static final long MINIMUM_ELAPSED_SECONDS_FOR_ETA = 5L; - - private final GeoPosition targetPosition; - - private GeoPosition startPosition; - private Instant startTimestamp; - - private double originalDistanceMeters; - private double currentDistanceMeters; - private double averageClosingSpeedMetersPerSecond; - private double progressPercentage; - private long estimatedSecondsToTarget; - - public RepositionTaskMonitor( - UUID taskId, - DroneTwin droneTwin, - String topicPath, - int taskSequence, - Duration timeout, - Duration feedbackInterval, - GeoPosition targetPosition, - AuditEvent auditEvent) { - super(taskId, droneTwin, topicPath, taskSequence, timeout, feedbackInterval, auditEvent); - this.targetPosition = targetPosition; - this.originalDistanceMeters = Double.MAX_VALUE; - this.currentDistanceMeters = Double.MAX_VALUE; - this.averageClosingSpeedMetersPerSecond = 0.0d; - this.progressPercentage = 0.0d; - this.estimatedSecondsToTarget = -1L; - } - - @Override - public String getTaskType() { - return TASK_TYPE; - } - - @Override - protected void updateTask(DroneTwin droneTwin) { - if (droneTwin == null || !getDroneUUID().equals(droneTwin.getUuid())) { - return; - } - - GeoPosition currentPosition = droneTwin.getGeoPosition(); - if (!hasUsablePosition(currentPosition) || !hasUsablePosition(targetPosition)) { - return; - } - - Instant currentTimestamp = Instant.now(); - - if (startPosition == null) { - initialisePosition(currentPosition, currentTimestamp); - setInProgress(); - return; - } - - updatePosition(currentPosition, currentTimestamp); - - if (currentDistanceMeters <= COMPLETE_DISTANCE_METERS) { - progressPercentage = 100.0d; - estimatedSecondsToTarget = 0L; - setComplete(); - return; - } - - setInProgress(); - } - - public TaskFeedbackMessage buildFeedback(TaskFeedbackMessageBuilder feedbackMessageBuilder) { - TaskFeedbackMessage feedbackMessage =super.buildFeedback(feedbackMessageBuilder); - feedbackMessage.getBody().setPercentComplete(getProgressPercentage()); - return feedbackMessage; - } - - private void initialisePosition(GeoPosition currentPosition, Instant currentTimestamp) { - startPosition = currentPosition; - startTimestamp = currentTimestamp; - - originalDistanceMeters = GeoUtils.distanceMeters(currentPosition, targetPosition); - currentDistanceMeters = originalDistanceMeters; - averageClosingSpeedMetersPerSecond = 0.0d; - progressPercentage = calculateProgressPercentage(); - estimatedSecondsToTarget = -1L; - } - - private void updatePosition(GeoPosition currentPosition, Instant currentTimestamp) { - currentDistanceMeters = GeoUtils.distanceMeters(currentPosition, targetPosition); - updateProgress(); - updateAverageClosingSpeed(currentTimestamp); - updateEstimatedSecondsToTarget(currentTimestamp); - } - - private void updateProgress() { - progressPercentage = calculateProgressPercentage(); - } - - private void updateAverageClosingSpeed(Instant currentTimestamp) { - long elapsedSeconds = Duration.between(startTimestamp, currentTimestamp).toSeconds(); - - if (elapsedSeconds <= 0L) { - averageClosingSpeedMetersPerSecond = 0.0d; - return; - } - - double closedDistanceMeters = originalDistanceMeters - currentDistanceMeters; - - if (closedDistanceMeters <= 0.0d) { - averageClosingSpeedMetersPerSecond = 0.0d; - return; - } - - averageClosingSpeedMetersPerSecond = closedDistanceMeters / elapsedSeconds; - } - - private double calculateProgressPercentage() { - if (originalDistanceMeters <= 0.0d) { - return 100.0d; - } - double progress = ((originalDistanceMeters - currentDistanceMeters) / originalDistanceMeters) * 100.0d; - return roundToOneDecimalPlace(Math.clamp(progress, 0.0d, 100.0d)); - } - - private void updateEstimatedSecondsToTarget(Instant currentTimestamp) { - long elapsedSeconds = Duration.between(startTimestamp, currentTimestamp).toSeconds(); - if (elapsedSeconds < MINIMUM_ELAPSED_SECONDS_FOR_ETA) { - estimatedSecondsToTarget = -1L; - return; - } - if (progressPercentage < MINIMUM_PROGRESS_PERCENTAGE_FOR_ETA) { - estimatedSecondsToTarget = -1L; - return; - } - if (averageClosingSpeedMetersPerSecond <= MINIMUM_CLOSING_SPEED_METERS_PER_SECOND) { - estimatedSecondsToTarget = -1L; - return; - } - estimatedSecondsToTarget = Math.round(currentDistanceMeters / averageClosingSpeedMetersPerSecond); - } - - private String buildTimeRemaining() { - long secondsToTarget = Math.max(0L, Math.round(estimatedSecondsToTarget)); - return Duration.ofSeconds(secondsToTarget).toString(); - } - - private boolean hasUsablePosition(GeoPosition position) { - return position != null - && position.getLatitude() != null - && position.getLongitude() != null; - } - - private double roundToOneDecimalPlace(double value) { - return Math.round(value * 10.0d) / 10.0d; - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitor.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitor.java deleted file mode 100644 index 294fd6d7a..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitor.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * - * 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.state.stanag.tasks.monitor; - -import io.mapsmessaging.state.config.capability.Authorities; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.audit.AuditEvent; -import io.mapsmessaging.state.stanag.messages.*; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessageBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessageBuilder; -import lombok.Getter; -import lombok.Setter; - -import java.time.Duration; -import java.time.Instant; -import java.util.UUID; - -import static io.mapsmessaging.state.stanag.messages.TaskState.LOST; - -@Getter -public abstract class TaskMonitor { - - private final UUID taskId; - private final UUID droneUUID; - private final int taskSequence; - private final Duration timeout; - private final Duration feedbackInterval; - private final Instant createdTimestamp; - private final AuditEvent auditEvent; - private final String topicPath; - - private TaskMonitorState state; - private Instant lastUpdatedTimestamp; - private Instant lastFeedbackTimestamp; - private boolean resultEmitted; - - - @Setter - private Authorities authority; - - protected TaskMonitor( - UUID taskId, - DroneTwin droneTwin, - String topicPath, - int taskSequence, - Duration timeout, - Duration feedbackInterval, - AuditEvent auditEvent) { - this.taskId = taskId; - this.droneUUID = droneTwin.getUuid(); - this.taskSequence = taskSequence; - this.timeout = timeout; - this.feedbackInterval = feedbackInterval; - this.auditEvent = auditEvent; - this.createdTimestamp = Instant.now(); - this.lastUpdatedTimestamp = createdTimestamp; - this.state = TaskMonitorState.ACCEPTED; - this.topicPath = topicPath; - } - - public abstract String getTaskType(); - - public final void update(DroneTwin droneTwin) { - if (isFinished()) { - return; - } - - Instant now = Instant.now(); - if (timeout != null && Duration.between(createdTimestamp, now).compareTo(timeout) > 0) { - setTimedOut(); - return; - } - - updateTask(droneTwin); - lastUpdatedTimestamp = now; - } - - protected abstract void updateTask(DroneTwin droneTwin); - - public boolean hasFeedback() { - if (isFinished()) { - return false; - } - - if (feedbackInterval == null) { - return false; - } - - if (lastFeedbackTimestamp == null) { - return true; - } - - return Duration.between(lastFeedbackTimestamp, Instant.now()).compareTo(feedbackInterval) >= 0; - } - - public TaskFeedbackMessage buildFeedback(TaskFeedbackMessageBuilder feedbackMessageBuilder) { - TaskFeedbackMessage feedbackMessage = feedbackMessageBuilder.build(buildStatusContext(), mapFeedbackState()); - markFeedbackEmitted(); - return feedbackMessage; - } - - public boolean hasResult() { - return isFinished() && !resultEmitted; - } - - public TaskResultMessage buildResult(TaskResultMessageBuilder resultMessageBuilder) { - TaskResultMessage resultMessage = resultMessageBuilder.build(buildStatusContext(), mapResultState(), buildResultReason(), null); - markResultEmitted(); - return resultMessage; - } - - public boolean isFinished() { - return state == TaskMonitorState.COMPLETE - || state == TaskMonitorState.FAILED - || state == TaskMonitorState.TIMEOUT - || state == TaskMonitorState.LOST; - } - - public void setAccepted() { - if (!isFinished()) { - state = TaskMonitorState.ACCEPTED; - } - } - - public void setInProgress() { - if (!isFinished()) { - state = TaskMonitorState.IN_PROGRESS; - } - } - - public void setComplete() { - if (!isFinished()) { - state = TaskMonitorState.COMPLETE; - } - } - - public void setFailed() { - if (!isFinished()) { - state = TaskMonitorState.FAILED; - } - } - - public void setTimedOut() { - if (!isFinished()) { - state = TaskMonitorState.TIMEOUT; - } - } - - public void setLost() { - if (!isFinished()) { - state = TaskMonitorState.LOST; - } - } - - protected ResultReason buildResultReason() { - return null; - } - - private TaskStatusContext buildStatusContext() { - return new TaskStatusContext(taskId, droneUUID, authority); - } - - private TaskState mapFeedbackState() { - return TaskState.ACTIVE; - } - - private TaskState mapResultState() { - return switch (state) { - case COMPLETE -> TaskState.SUCCEEDED; - case FAILED -> TaskState.ABORTED; - case TIMEOUT, LOST -> LOST; - case ACCEPTED, IN_PROGRESS -> TaskState.ACTIVE; - }; - } - - private void markFeedbackEmitted() { - lastFeedbackTimestamp = Instant.now(); - } - - private void markResultEmitted() { - resultEmitted = true; - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorManager.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorManager.java deleted file mode 100644 index faaeec530..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskMonitorManager.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * - * 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.state.stanag.tasks.monitor; - -import io.mapsmessaging.state.drone.core.EntityTwin; -import io.mapsmessaging.state.drone.core.TwinObserver; -import io.mapsmessaging.state.drone.core.TwinUpdateContext; -import io.mapsmessaging.state.drone.drone.DroneTwin; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessageBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReasonBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessageBuilder; -import io.mapsmessaging.utilities.threads.SimpleTaskScheduler; -import lombok.Getter; - -import java.time.Clock; -import java.time.Duration; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class TaskMonitorManager implements AutoCloseable, TwinObserver { - - private final Duration scanInterval; - private final TaskStatusPublisher statusPublisher; - - private final TaskFeedbackMessageBuilder feedbackMessageBuilder; - private final TaskResultMessageBuilder resultMessageBuilder; - - @Getter - private final List taskMonitors = new CopyOnWriteArrayList<>(); - - private final AtomicBoolean running = new AtomicBoolean(false); - - public TaskMonitorManager(Duration scanInterval, TaskStatusPublisher statusPublisher) { - this.scanInterval = scanInterval; - this.statusPublisher = statusPublisher; - - Clock clock = Clock.systemUTC(); - MessageHeaderBuilder messageHeaderBuilder = new MessageHeaderBuilder(clock); - feedbackMessageBuilder = new TaskFeedbackMessageBuilder(messageHeaderBuilder); - ResultReasonBuilder resultReasonBuilder = new ResultReasonBuilder(); - resultMessageBuilder = new TaskResultMessageBuilder(messageHeaderBuilder, resultReasonBuilder); - } - - public void start() { - if (!running.compareAndSet(false, true)) { - return; - } - SimpleTaskScheduler.getInstance().scheduleAtFixedRate(this::scanTaskMonitors, scanInterval.toMillis(), scanInterval.toMillis(), TimeUnit.MILLISECONDS); - } - - public void add(TaskMonitor taskMonitor) { - if (taskMonitor == null) { - return; - } - taskMonitors.add(taskMonitor); - } - - public void update(DroneTwin droneTwin) { - if (droneTwin == null) { - return; - } - - for (TaskMonitor taskMonitor : taskMonitors) { - if (taskMonitor.getDroneUUID().equals(droneTwin.getUuid())) { - taskMonitor.update(droneTwin); - } - } - } - - public void scanTaskMonitors() { - if (!running.get()) { - return; - } - - for (TaskMonitor taskMonitor : taskMonitors) { - scanTaskMonitor(taskMonitor); - } - } - - @Override - public void close() { - running.set(false); - taskMonitors.clear(); - } - - private void scanTaskMonitor(TaskMonitor taskMonitor) { - if (taskMonitor.hasResult()) { - publishResult(taskMonitor); - taskMonitors.remove(taskMonitor); - return; - } - - if (taskMonitor.hasFeedback()) { - publishFeedback(taskMonitor); - } - } - - private void publishFeedback(TaskMonitor taskMonitor) { - TaskFeedbackMessage taskFeedbackMessage = taskMonitor.buildFeedback(feedbackMessageBuilder); - - statusPublisher.publishFeedback(taskMonitor, taskFeedbackMessage); - } - - private void publishResult(TaskMonitor taskMonitor) { - TaskResultMessage taskResultMessage = taskMonitor.buildResult(resultMessageBuilder); - - statusPublisher.publishResult(taskMonitor, taskResultMessage); - } - - - @Override - public void onTwinUpdated(String twinId, EntityTwin current, TwinUpdateContext context) { - if (!(current instanceof DroneTwin droneTwin)) { - return; - } - - update(droneTwin); - } - - @Override - public void onTwinRemoved(EntityTwin removed, TwinUpdateContext context) { - if (!(removed instanceof DroneTwin droneTwin)) { - return; - } - - cancelTaskMonitorsForDrone(droneTwin.getUuid()); - } - - private void cancelTaskMonitorsForDrone(UUID droneId) { - for (TaskMonitor taskMonitor : taskMonitors) { - if (!taskMonitor.getDroneUUID().equals(droneId)) { - continue; - } - - taskMonitor.setLost(); - - if (taskMonitor.hasResult()) { - publishResult(taskMonitor); - } - - taskMonitors.remove(taskMonitor); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskStatusPublisher.java b/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskStatusPublisher.java deleted file mode 100644 index 8074b0862..000000000 --- a/src/main/java/io/mapsmessaging/state/stanag/tasks/monitor/TaskStatusPublisher.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * - * 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.state.stanag.tasks.monitor; - -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; - -public interface TaskStatusPublisher { - - void publishFeedback(TaskMonitor taskMonitor, TaskFeedbackMessage taskFeedbackMessage); - - void publishResult(TaskMonitor taskMonitor, TaskResultMessage taskResultMessage); -} \ No newline at end of file diff --git a/src/main/resources/META-INF/services/io.mapsmessaging.state.stanag.tasks.TaskHandler b/src/main/resources/META-INF/services/io.mapsmessaging.state.stanag.tasks.TaskHandler deleted file mode 100644 index 47adc6214..000000000 --- a/src/main/resources/META-INF/services/io.mapsmessaging.state.stanag.tasks.TaskHandler +++ /dev/null @@ -1,20 +0,0 @@ -# -# -# 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. -# - -io.mapsmessaging.state.stanag.tasks.RepositionTaskHandler \ No newline at end of file From 3f3b3ae8c8bcf8afb162ef956d2e43964dbb4d98 Mon Sep 17 00:00:00 2001 From: Joshua Buckton Date: Wed, 24 Jun 2026 11:41:16 +1000 Subject: [PATCH 2/4] test(state): cover generic state adapter APIs Add focused coverage for the explicit stateAdapters configuration map and generic audit context factory behavior. --- .../state/auditor/AuditorFactoryTest.java | 49 ++++++ .../state/config/TwinManagerConfigTest.java | 139 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 src/test/java/io/mapsmessaging/state/auditor/AuditorFactoryTest.java create mode 100644 src/test/java/io/mapsmessaging/state/config/TwinManagerConfigTest.java diff --git a/src/test/java/io/mapsmessaging/state/auditor/AuditorFactoryTest.java b/src/test/java/io/mapsmessaging/state/auditor/AuditorFactoryTest.java new file mode 100644 index 000000000..bffe66506 --- /dev/null +++ b/src/test/java/io/mapsmessaging/state/auditor/AuditorFactoryTest.java @@ -0,0 +1,49 @@ +/* + * + * 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.state.auditor; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import io.mapsmessaging.state.drone.core.TwinManager; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class AuditorFactoryTest { + + @TempDir + private Path tempDir; + + @Test + void build_returnsGenericAuditContextThatTwinManagerCanExpose() throws Exception { + AuditorFactory factory = new AuditorFactory(); + + try (AuditorFactory.AuditorInstance auditorInstance = factory.build(tempDir)) { + StateAuditContext auditContext = auditorInstance.getAuditContext(); + TwinManager twinManager = new TwinManager(true, 10000L, 5000L, 120000L, auditContext); + + assertNotNull(auditContext); + assertNotNull(auditContext.auditLogger()); + assertNotNull(auditContext.auditPayloadStore()); + assertSame(auditContext, twinManager.getAuditContext()); + } + } +} diff --git a/src/test/java/io/mapsmessaging/state/config/TwinManagerConfigTest.java b/src/test/java/io/mapsmessaging/state/config/TwinManagerConfigTest.java new file mode 100644 index 000000000..e71a82457 --- /dev/null +++ b/src/test/java/io/mapsmessaging/state/config/TwinManagerConfigTest.java @@ -0,0 +1,139 @@ +/* + * + * 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.state.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.mapsmessaging.configuration.ConfigurationProperties; +import io.mapsmessaging.state.config.n2k.N2KTwinConfig; +import java.lang.reflect.Constructor; +import org.junit.jupiter.api.Test; + +class TwinManagerConfigTest { + + @Test + void dto_adapterConfig_defaultsEmptyAndAcceptsMultipleAdapterKeys() { + TwinManagerConfigDTO config = new TwinManagerConfigDTO(); + ConfigurationProperties firstAdapter = adapterProperties("first/topic", 11); + ConfigurationProperties secondAdapter = adapterProperties("second/topic", 22); + + config.getAdapterConfig().put("alphaAdapter", firstAdapter); + config.getAdapterConfig().put("bravoAdapter", secondAdapter); + + assertEquals(2, config.getAdapterConfig().size()); + assertSame(firstAdapter, config.getAdapterConfig().get("alphaAdapter")); + assertSame(secondAdapter, config.getAdapterConfig().get("bravoAdapter")); + } + + @Test + void constructor_preservesArbitraryAdapterBlocks() throws ReflectiveOperationException { + ConfigurationProperties root = new ConfigurationProperties(); + root.put("heartbeatTimeoutMillis", 5000L); + root.put("stateAdapters", stateAdapterConfig( + "alphaAdapter", adapterProperties("alpha/topic", 11), + "bravoAdapter", adapterProperties("bravo/topic", 22) + )); + + TwinManagerConfig config = newTwinManagerConfig(root); + + assertEquals(2, config.getAdapterConfig().size()); + assertEquals("alpha/topic", config.getAdapterConfig().get("alphaAdapter").getProperty("topic")); + assertEquals(11, config.getAdapterConfig().get("alphaAdapter").getIntProperty("interval", 0)); + assertEquals("bravo/topic", config.getAdapterConfig().get("bravoAdapter").getProperty("topic")); + assertEquals(22, config.getAdapterConfig().get("bravoAdapter").getIntProperty("interval", 0)); + } + + @Test + void constructor_ignoresTopLevelAdapterLikeBlocks() throws ReflectiveOperationException { + ConfigurationProperties root = new ConfigurationProperties(); + root.put("alphaAdapter", adapterProperties("alpha/topic", 5)); + + TwinManagerConfig config = newTwinManagerConfig(root); + + assertTrue(config.getAdapterConfig().isEmpty()); + } + + @Test + void toConfigurationProperties_writesAllAdapterBlocksBack() throws ReflectiveOperationException { + ConfigurationProperties root = new ConfigurationProperties(); + ConfigurationProperties firstAdapter = adapterProperties("alpha/topic", 11); + ConfigurationProperties secondAdapter = adapterProperties("bravo/topic", 22); + root.put("stateAdapters", stateAdapterConfig( + "alphaAdapter", firstAdapter, + "bravoAdapter", secondAdapter + )); + + TwinManagerConfig config = newTwinManagerConfig(root); + ConfigurationProperties saved = config.toConfigurationProperties(); + ConfigurationProperties savedAdapters = assertInstanceOf(ConfigurationProperties.class, saved.get("stateAdapters")); + + assertSame(firstAdapter, savedAdapters.get("alphaAdapter")); + assertSame(secondAdapter, savedAdapters.get("bravoAdapter")); + assertNull(saved.get("alphaAdapter")); + assertNull(saved.get("bravoAdapter")); + } + + @Test + void update_copiesAdapterConfigMap() { + TwinManagerConfig config = new TwinManagerConfig(); + config.setN2KTwinConfig(new N2KTwinConfig()); + TwinManagerConfigDTO newConfig = new TwinManagerConfigDTO(); + newConfig.setN2KTwinConfig(new N2KTwinConfig()); + ConfigurationProperties adapter = adapterProperties("alpha/topic", 11); + newConfig.getAdapterConfig().put("alphaAdapter", adapter); + + boolean changed = config.update(newConfig); + + assertTrue(changed); + assertEquals(1, config.getAdapterConfig().size()); + assertSame(adapter, config.getAdapterConfig().get("alphaAdapter")); + } + + private ConfigurationProperties adapterProperties(String topic, int interval) { + ConfigurationProperties properties = new ConfigurationProperties(); + properties.put("enabled", true); + properties.put("topic", topic); + properties.put("interval", interval); + return properties; + } + + private ConfigurationProperties stateAdapterConfig( + String firstName, + ConfigurationProperties firstProperties, + String secondName, + ConfigurationProperties secondProperties + ) { + ConfigurationProperties stateAdapters = new ConfigurationProperties(); + stateAdapters.put(firstName, firstProperties); + stateAdapters.put(secondName, secondProperties); + return stateAdapters; + } + + private TwinManagerConfig newTwinManagerConfig(ConfigurationProperties properties) + throws ReflectiveOperationException { + Constructor constructor = TwinManagerConfig.class.getDeclaredConstructor(ConfigurationProperties.class); + constructor.setAccessible(true); + return constructor.newInstance(properties); + } +} From 4ad188a15bab9b427e000a1fd82837dbb15b389c Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 03:42:54 +0000 Subject: [PATCH 3/4] fix: apply CodeRabbit auto-fixes Fixed 4 file(s) based on 3 unresolved review comments. Co-authored-by: CodeRabbit --- pom.xml | 3 ++- src/main/java/io/mapsmessaging/logging/ServerLogMessages.java | 1 + src/main/java/io/mapsmessaging/state/StateManagerAgent.java | 2 +- .../java/io/mapsmessaging/state/config/TwinManagerConfig.java | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5304d765a..90e002590 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,7 @@ 1.2.3-SNAPSHOT 2.0.1-SNAPSHOT 1.1.1-SNAPSHOT + 1.0.0-SNAPSHOT 1.1.1-SNAPSHOT @@ -684,7 +685,7 @@ io.mapsmessaging mapsmessaging-stanag-state-adapter - ${project.version} + ${maps.stanag.version} diff --git a/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java b/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java index be84f30b9..c2251f52d 100644 --- a/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java +++ b/src/main/java/io/mapsmessaging/logging/ServerLogMessages.java @@ -933,6 +933,7 @@ public enum ServerLogMessages implements LogMessage { STATE_MANAGER_PUBLISH_ENABLED(LEVEL.INFO, SERVER_CATEGORY.STATE, "Twin JSON publisher enabled with topic {}"), STATE_MANAGER_PUBLISH_FAILED(LEVEL.ERROR, SERVER_CATEGORY.STATE, "Failed to start Twin JSON publisher"), STATE_MANAGER_SCHEDULER_ERROR(LEVEL.ERROR, SERVER_CATEGORY.STATE, "Scheduler task failed"), + STATE_MANAGER_AUDIT_INIT_FAILED(LEVEL.ERROR, SERVER_CATEGORY.STATE, "Failed to initialize audit context - auditing will be disabled"), // //------------------------------------------------------------------------------------------------------------- diff --git a/src/main/java/io/mapsmessaging/state/StateManagerAgent.java b/src/main/java/io/mapsmessaging/state/StateManagerAgent.java index 69b9d9a34..28db55e96 100644 --- a/src/main/java/io/mapsmessaging/state/StateManagerAgent.java +++ b/src/main/java/io/mapsmessaging/state/StateManagerAgent.java @@ -74,7 +74,7 @@ public StateManagerAgent() { auditorInstance = factory.build(Path.of( EnvironmentConfig.getInstance().getPathLookups().get("MAPS_DATA"))); auditContext = auditorInstance.getAuditContext(); } catch (IOException e) { - e.printStackTrace(); + logger.log(STATE_MANAGER_AUDIT_INIT_FAILED, e); } twinManager = new TwinManager(config.isRemoveExpiredTwins(), config.getStaleTimeoutMillis(), config.getHeartbeatTimeoutMillis(), config.getRetentionTimeoutMillis(), auditContext); registry = new DroneInfoRegistry(config.getDroneInfo()); diff --git a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java index 8ad718b38..4c7f787ae 100644 --- a/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java +++ b/src/main/java/io/mapsmessaging/state/config/TwinManagerConfig.java @@ -227,7 +227,7 @@ public boolean update(BaseConfigDTO config) { hasChanged = true; } - if (this.adapterConfig != newConfig.getAdapterConfig()) { + if (newConfig.getAdapterConfig() != null && !this.adapterConfig.equals(newConfig.getAdapterConfig())) { this.adapterConfig = new LinkedHashMap<>(newConfig.getAdapterConfig()); hasChanged = true; } From 72c02904389c23cccf37882349dfa6d82bd1e70e Mon Sep 17 00:00:00 2001 From: Joshua Buckton Date: Wed, 24 Jun 2026 14:19:04 +1000 Subject: [PATCH 4/4] Remove left over test case from refactor --- pom.xml | 6 - .../TaskStatusMessageBuilderTest.java | 172 ------------------ 2 files changed, 178 deletions(-) delete mode 100644 src/test/java/io/mapsmessaging/state/stanag/tasks/messages/TaskStatusMessageBuilderTest.java diff --git a/pom.xml b/pom.xml index 90e002590..9d6d98d5b 100644 --- a/pom.xml +++ b/pom.xml @@ -140,7 +140,6 @@ 1.2.3-SNAPSHOT 2.0.1-SNAPSHOT 1.1.1-SNAPSHOT - 1.0.0-SNAPSHOT 1.1.1-SNAPSHOT @@ -682,11 +681,6 @@ - - io.mapsmessaging - mapsmessaging-stanag-state-adapter - ${maps.stanag.version} - io.mapsmessaging diff --git a/src/test/java/io/mapsmessaging/state/stanag/tasks/messages/TaskStatusMessageBuilderTest.java b/src/test/java/io/mapsmessaging/state/stanag/tasks/messages/TaskStatusMessageBuilderTest.java deleted file mode 100644 index a3ee390af..000000000 --- a/src/test/java/io/mapsmessaging/state/stanag/tasks/messages/TaskStatusMessageBuilderTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * - * 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.state.stanag.tasks.messages; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; -import java.util.UUID; - -import io.mapsmessaging.state.GsonStanagHelper; - -import io.mapsmessaging.state.stanag.messages.*; -import io.mapsmessaging.state.stanag.messages.core.MessageHeaderBuilder; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessage; -import io.mapsmessaging.state.stanag.messages.task.feedback.TaskFeedbackMessageBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReason; -import io.mapsmessaging.state.stanag.messages.task.result.ResultReasonBuilder; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessage; -import io.mapsmessaging.state.stanag.messages.task.result.TaskResultMessageBuilder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class TaskStatusMessageBuilderTest { - - private static final UUID TASK_IDENTIFIER = UUID.fromString("7f1f8d0b-91bc-4f89-ae4e-5cb2e6d9a6a8"); - private static final UUID NODE_IDENTIFIER = UUID.fromString("53a15a3e-b8da-5dbe-bb9d-fdf3a8ff8159"); - private static final Instant FIXED_TIME = Instant.parse("2026-06-05T06:16:05.021Z"); - - private Gson gson; - private TaskStatusContext context; - private TaskFeedbackMessageBuilder feedbackMessageBuilder; - private TaskResultMessageBuilder resultMessageBuilder; - - @BeforeEach - void setUp() { - gson = GsonStanagHelper.createGson(); - - Clock clock = Clock.fixed(FIXED_TIME, ZoneOffset.UTC); - MessageHeaderBuilder headerBuilder = new MessageHeaderBuilder(clock); - - feedbackMessageBuilder = new TaskFeedbackMessageBuilder(headerBuilder); - resultMessageBuilder = new TaskResultMessageBuilder(headerBuilder, new ResultReasonBuilder()); - - context = new TaskStatusContext(TASK_IDENTIFIER, NODE_IDENTIFIER, null); - } - - - @Test - void shouldBuildProgressFeedbackMessage() { - TaskFeedbackMessage message = feedbackMessageBuilder.buildProgress(context, 42.5d); - - JsonObject body = toJsonObject(message).getAsJsonObject("body"); - - assertEquals(TASK_IDENTIFIER, body.get("identifier").getAsString()); - assertEquals(NODE_IDENTIFIER, body.get("node").getAsString()); - assertEquals("TaskStateEnum_ACTIVE", body.get("state").getAsString()); - assertEquals(42.5d, body.get("percent_complete").getAsDouble(), 0.0001d); - - assertFalse(body.has("time_remaining")); - assertFalse(body.has("waypoints_remaining")); - } - - @Test - void shouldRejectProgressLessThanZero() { - assertThrows( - IllegalArgumentException.class, - () -> feedbackMessageBuilder.buildProgress(context, -0.1d)); - } - - @Test - void shouldRejectProgressGreaterThanOneHundred() { - assertThrows( - IllegalArgumentException.class, - () -> feedbackMessageBuilder.buildProgress(context, 100.1d)); - } - - @Test - void shouldNotAllowTerminalStateInFeedbackMessage() { - assertThrows( - IllegalArgumentException.class, - () -> feedbackMessageBuilder.build(context, TaskState.SUCCEEDED)); - } - - @Test - void shouldBuildSucceededResultMessage() { - TaskResultMessage message = resultMessageBuilder.buildSucceeded(context); - - JsonObject json = toJsonObject(message); - JsonObject header = json.getAsJsonObject("header"); - JsonObject body = json.getAsJsonObject("body"); - - assertEquals("MessageTypeEnum_TASK_RESULT", header.get("message_type").getAsString()); - assertEquals(NODE_IDENTIFIER, header.get("source").getAsString()); - assertEquals("2026-06-05T06:16:05.021Z", header.get("time_sent").getAsString()); - assertEquals("0.3.0", header.get("version").getAsString()); - - assertEquals(TASK_IDENTIFIER, body.get("identifier").getAsString()); - assertEquals(NODE_IDENTIFIER, body.get("node").getAsString()); - assertEquals("TaskStateEnum_SUCCEEDED", body.get("state").getAsString()); - - assertFalse(body.has("result_reason")); - } - - @Test - void shouldBuildRejectedResultMessageWithReason() { - TaskResultMessage message = resultMessageBuilder.buildRejected(context, ResultReason.SAFETY, "Safety reason"); - - JsonObject body = toJsonObject(message).getAsJsonObject("body"); - JsonObject resultReason = body.getAsJsonObject("result_reason"); - - assertEquals(TASK_IDENTIFIER, body.get("identifier").getAsString()); - assertEquals(NODE_IDENTIFIER, body.get("node").getAsString()); - assertEquals("TaskStateEnum_REJECTED", body.get("state").getAsString()); - assertEquals("ResultReasonEnum_SAFETY", resultReason.get("name").getAsString()); - } - - @Test - void shouldBuildAbortedResultMessageWithReason() { - TaskResultMessage message = resultMessageBuilder.buildAborted(context, ResultReason.HARDWARE, "Hardware reason"); - - JsonObject body = toJsonObject(message).getAsJsonObject("body"); - JsonObject resultReason = body.getAsJsonObject("result_reason"); - - assertEquals("TaskStateEnum_ABORTED", body.get("state").getAsString()); - assertEquals("ResultReasonEnum_HARDWARE", resultReason.get("name").getAsString()); - } - - @Test - void shouldBuildLostResultMessageWithReason() { - TaskResultMessage message = resultMessageBuilder.buildLost(context, ResultReason.COMMUNICATION, "Communication reason"); - - JsonObject body = toJsonObject(message).getAsJsonObject("body"); - JsonObject resultReason = body.getAsJsonObject("result_reason"); - - assertEquals("TaskStateEnum_LOST", body.get("state").getAsString()); - assertEquals("ResultReasonEnum_COMMUNICATION", resultReason.get("name").getAsString()); - } - - @Test - void shouldNotAllowNonTerminalStateInResultMessage() { - assertThrows( - IllegalArgumentException.class, - () -> resultMessageBuilder.build(context, TaskState.ACTIVE, null, null)); - } - - private JsonObject toJsonObject(Object value) { - return gson.toJsonTree(value).getAsJsonObject(); - } -} \ No newline at end of file