diff --git a/constraints/build.gradle b/constraints/build.gradle index 0bf3022b69..4d6eb916bb 100644 --- a/constraints/build.gradle +++ b/constraints/build.gradle @@ -30,8 +30,8 @@ repositories { dependencies { implementation project(':merlin-driver') - implementation project(':merlin-sdk') implementation project(':parsing-utilities') + implementation project(':type-utils') testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/ActivityInstance.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/ActivityInstance.java index 96e9144dc3..8a3c8758c3 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/ActivityInstance.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/ActivityInstance.java @@ -1,12 +1,11 @@ package gov.nasa.jpl.aerie.constraints.model; import gov.nasa.jpl.aerie.constraints.time.Interval; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Map; -import java.util.Objects; import java.util.Optional; public record ActivityInstance( diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java index 4e3ae03126..222413bb40 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/BindingsTests.java @@ -1118,7 +1118,7 @@ void invalidModelId(){ assertEquals(200, response.status()); final var expectedBody = Json.createObjectBuilder() .add("status", "failure") - .add("reason", "No mission model exists with id `MissionModelId[id=-1]`") + .add("reason", "No mission model exists with id `-1`") .build(); assertEquals(expectedBody, getBody(response)); } diff --git a/examples/banananation/build.gradle b/examples/banananation/build.gradle index 19cdec5193..6de69cccb3 100644 --- a/examples/banananation/build.gradle +++ b/examples/banananation/build.gradle @@ -43,6 +43,8 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.13.0' testImplementation project(':merlin-framework-junit') + testImplementation project(':orchestration-utils') + testImplementation project(':type-utils') testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' diff --git a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/ActivityInstanceTest.java b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/ActivityInstanceTest.java index ec44be8cf9..717a92500c 100644 --- a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/ActivityInstanceTest.java +++ b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/ActivityInstanceTest.java @@ -1,12 +1,12 @@ package gov.nasa.jpl.aerie.banananation; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.engine.SimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.timeline.TemporalEventSource; import gov.nasa.jpl.aerie.merlin.protocol.driver.Topic; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; diff --git a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/Main.java b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/Main.java index 11148a57ff..63c86536bc 100644 --- a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/Main.java +++ b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/Main.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.banananation; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.tuple.Pair; import java.util.Map; diff --git a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/SimulationUtility.java b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/SimulationUtility.java index 65adba4bc1..e0383b3a08 100644 --- a/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/SimulationUtility.java +++ b/examples/banananation/src/test/java/gov/nasa/jpl/aerie/banananation/SimulationUtility.java @@ -2,39 +2,45 @@ import gov.nasa.jpl.aerie.banananation.generated.GeneratedModelType; import gov.nasa.jpl.aerie.merlin.driver.*; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.SerializedActivity; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import java.nio.file.Path; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutionException; public final class SimulationUtility { - private static MissionModel makeMissionModel(final MissionModelBuilder builder, final Instant planStart, final Configuration config) { - final var factory = new GeneratedModelType(); - final var registry = DirectiveTypeRegistry.extract(factory); - // TODO: [AERIE-1516] Teardown the model to release any system resources (e.g. threads). - final var model = factory.instantiate(planStart, config, builder); - return builder.build(model, registry); - } - public static SimulationResults simulate(final Map schedule, final Duration simulationDuration) { final var dataPath = Path.of(SimulationUtility.class.getResource("data/lorem_ipsum.txt").getPath()); final var config = new Configuration(Configuration.DEFAULT_PLANT_COUNT, Configuration.DEFAULT_PRODUCER, dataPath, Configuration.DEFAULT_INITIAL_CONDITIONS); final var startTime = Instant.now(); - final var missionModel = makeMissionModel(new MissionModelBuilder(), Instant.EPOCH, config); - - return SimulationDriver.simulate( - missionModel, + final var missionModel = gov.nasa.jpl.aerie.orchestration.simulation.SimulationUtility.instantiateMissionModel( + new GeneratedModelType(), + Instant.EPOCH, + config); + + final var plan = new Plan( + "plan", + new Timestamp(startTime), + new Timestamp(startTime.plus(simulationDuration.in(Duration.MICROSECOND), ChronoUnit.MICROS)), schedule, - startTime, - simulationDuration, - startTime, - simulationDuration, - () -> false); + Map.of("initialDataPath", SerializedValue.of(dataPath.toString()))); + + try(final var simUtil = new gov.nasa.jpl.aerie.orchestration.simulation.SimulationUtility()) { + return simUtil.simulate(missionModel, plan).get(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } } @SafeVarargs diff --git a/examples/foo-missionmodel/build.gradle b/examples/foo-missionmodel/build.gradle index 5e8bcd7354..f74026a8de 100644 --- a/examples/foo-missionmodel/build.gradle +++ b/examples/foo-missionmodel/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation project(':merlin-framework') implementation project(':contrib') + implementation project(':type-utils') testImplementation project(':merlin-framework-junit') testImplementation 'org.assertj:assertj-core:3.24.2' diff --git a/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/FooSimulationDuplicationTest.java b/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/FooSimulationDuplicationTest.java index f183d23824..4da242a48e 100644 --- a/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/FooSimulationDuplicationTest.java +++ b/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/FooSimulationDuplicationTest.java @@ -1,21 +1,21 @@ package gov.nasa.jpl.aerie.foomissionmodel; import gov.nasa.jpl.aerie.foomissionmodel.generated.GeneratedModelType; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.CachedEngineStore; import gov.nasa.jpl.aerie.merlin.driver.CachedSimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.CheckpointSimulationDriver; import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.MissionModelBuilder; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.SimulationDriver; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.framework.ThreadedTask; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/SimulateMapSchedule.java b/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/SimulateMapSchedule.java index a5eafc5d2f..63f65609ed 100644 --- a/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/SimulateMapSchedule.java +++ b/examples/foo-missionmodel/src/test/java/gov/nasa/jpl/aerie/foomissionmodel/SimulateMapSchedule.java @@ -2,11 +2,11 @@ import gov.nasa.jpl.aerie.foomissionmodel.generated.GeneratedModelType; import gov.nasa.jpl.aerie.merlin.driver.*; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.json.JsonEncoding; -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import org.apache.commons.lang3.tuple.Pair; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import javax.json.Json; import java.time.Instant; diff --git a/merlin-driver/build.gradle b/merlin-driver/build.gradle index c6ad4e9c7d..a07b237cb7 100644 --- a/merlin-driver/build.gradle +++ b/merlin-driver/build.gradle @@ -39,6 +39,7 @@ dependencies { api project(':merlin-sdk') api 'org.glassfish:javax.json:1.1.4' + implementation project(':type-utils') implementation 'it.unimi.dsi:fastutil:8.5.12' implementation 'org.slf4j:slf4j-simple:2.0.7' diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityInstance.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityInstance.java index 48d1a0dc49..4fa9e27b24 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityInstance.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityInstance.java @@ -2,6 +2,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.time.Instant; import java.util.List; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CachedSimulationEngine.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CachedSimulationEngine.java index 2ce636965a..ae55cdddd6 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CachedSimulationEngine.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CachedSimulationEngine.java @@ -5,6 +5,8 @@ import gov.nasa.jpl.aerie.merlin.driver.resources.InMemorySimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.driver.Topic; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.time.Instant; import java.util.Map; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CheckpointSimulationDriver.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CheckpointSimulationDriver.java index 6f6d42cee8..4ec5787a90 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CheckpointSimulationDriver.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/CheckpointSimulationDriver.java @@ -9,6 +9,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.model.TaskFactory; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModel.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModel.java index fc9b477e34..8cbea7efbb 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModel.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModel.java @@ -11,6 +11,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; import gov.nasa.jpl.aerie.merlin.protocol.types.TaskStatus; import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; +import gov.nasa.jpl.aerie.types.SerializedActivity; import java.util.Collections; import java.util.List; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModelId.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModelId.java deleted file mode 100644 index 5f3d95bd8c..0000000000 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/MissionModelId.java +++ /dev/null @@ -1,3 +0,0 @@ -package gov.nasa.jpl.aerie.merlin.driver; - -public record MissionModelId(long id) {} diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDriver.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDriver.java index 29f7ea7dcf..ae6e4f1332 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDriver.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDriver.java @@ -10,6 +10,9 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationEngineConfiguration.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationEngineConfiguration.java index 3f1f4e4294..bfef673b75 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationEngineConfiguration.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationEngineConfiguration.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.merlin.driver; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.MissionModelId; import java.time.Instant; import java.util.Map; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationException.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationException.java index 87548c924e..d74cd39a89 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationException.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationException.java @@ -1,6 +1,8 @@ package gov.nasa.jpl.aerie.merlin.driver; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import java.time.Instant; import java.time.ZoneOffset; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationResultsComputerInputs.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationResultsComputerInputs.java index 6f34c1504f..50edc898fb 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationResultsComputerInputs.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SimulationResultsComputerInputs.java @@ -3,9 +3,8 @@ import gov.nasa.jpl.aerie.merlin.driver.engine.SimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.engine.SpanId; import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; -import gov.nasa.jpl.aerie.merlin.driver.timeline.TemporalEventSource; import gov.nasa.jpl.aerie.merlin.protocol.driver.Topic; -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.time.Instant; import java.util.Map; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/StartOffsetReducer.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/StartOffsetReducer.java index 3611545ea9..c86f3cd35b 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/StartOffsetReducer.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/StartOffsetReducer.java @@ -1,6 +1,8 @@ package gov.nasa.jpl.aerie.merlin.driver; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/UnfinishedActivity.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/UnfinishedActivity.java index 7e87f20582..b6ef8f4a60 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/UnfinishedActivity.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/UnfinishedActivity.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.merlin.driver; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.time.Instant; import java.util.List; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/DirectiveDetail.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/DirectiveDetail.java index ab2531069c..5a48ba83f4 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/DirectiveDetail.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/DirectiveDetail.java @@ -1,8 +1,7 @@ package gov.nasa.jpl.aerie.merlin.driver.engine; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.List; import java.util.Optional; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/SimulationEngine.java b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/SimulationEngine.java index d57945b7f9..f1000b8bdd 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/SimulationEngine.java +++ b/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/engine/SimulationEngine.java @@ -1,8 +1,6 @@ package gov.nasa.jpl.aerie.merlin.driver.engine; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModel.SerializableTopic; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; @@ -28,6 +26,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.TaskStatus; import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.mutable.MutableObject; diff --git a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/AnchorSimulationTest.java b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/AnchorSimulationTest.java index 976f9d4457..2e87d5e944 100644 --- a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/AnchorSimulationTest.java +++ b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/AnchorSimulationTest.java @@ -2,6 +2,9 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; diff --git a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDuplicationTest.java b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDuplicationTest.java index 24286636ab..abf7213e42 100644 --- a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDuplicationTest.java +++ b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/SimulationDuplicationTest.java @@ -2,6 +2,7 @@ import gov.nasa.jpl.aerie.merlin.driver.resources.InMemorySimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/TemporalSubsetSimulationTests.java b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/TemporalSubsetSimulationTests.java index 3df904ceee..cd67abdd32 100644 --- a/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/TemporalSubsetSimulationTests.java +++ b/merlin-driver/src/test/java/gov/nasa/jpl/aerie/merlin/driver/TemporalSubsetSimulationTests.java @@ -2,6 +2,9 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java b/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java index 561e8eb71e..42e7a90fca 100644 --- a/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java +++ b/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java @@ -2,6 +2,9 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A signed measure of the temporal distance between two instants. @@ -165,6 +168,36 @@ public static Duration of(final long quantity, final Duration unit) { return unit.times(quantity); } + /** Construct a duration from a string representation. Only accepts input in the format HH:MM:SS.ssssss */ + public static Duration fromString(final String duration) { + final var regexp = "([+-]?)(\\d{2,}):(\\d{2}):(\\d{2})(\\.\\d{1,6})?"; + + final Pattern pattern = Pattern.compile(regexp, Pattern.MULTILINE); + final Matcher matcher = pattern.matcher(duration); + + if (!matcher.matches()) { /// Unit test for this matcher.results().count() != 1 + throw new IllegalArgumentException("Duration has incorrect format. Expected format HH:MM:SS. Provided duration: " + + duration); + } + final var sign = Optional.ofNullable(matcher.group(1)); + final var hours = Duration.of(Integer.parseInt(matcher.group(2)),Duration.HOURS); + final var minutes = Duration.of(Integer.parseInt(matcher.group(3)),Duration.MINUTES); + final var seconds = Duration.of(Integer.parseInt(matcher.group(4)),Duration.SECONDS); + final var microsecondString = Optional.ofNullable(matcher.group(5)); + var micros = Duration.ZERO; + + if (microsecondString.isPresent()){ + var subSecond = microsecondString.get().substring(1); + if (subSecond.length() < 6){ + // append the missing zeros. + subSecond=subSecond+"0".repeat(6-subSecond.length()); + } + micros = Duration.of(Integer.parseInt(subSecond), Duration.MICROSECONDS); + } + + return micros.plus(seconds).plus(minutes).plus(hours).times(sign.isPresent() && sign.get().equals("-") ? -1 : 1); + } + /** * Construct a duration as a multiple of some unit. * @@ -504,6 +537,7 @@ public boolean isZero() { } public boolean isEqualTo(final Duration other) { + if(other == null) return false; return this.durationInMicroseconds == other.durationInMicroseconds; } @@ -511,9 +545,7 @@ public boolean isEqualTo(final Duration other) { @Override @Deprecated public boolean equals(final Object o) { - if (!(o instanceof Duration)) return false; - final var other = (Duration)o; - + if (!(o instanceof final Duration other)) return false; return this.isEqualTo(other); } diff --git a/merlin-sdk/src/test/java/gov/nasa/jpl/aerie/merlin/protocol/types/DurationTest.java b/merlin-sdk/src/test/java/gov/nasa/jpl/aerie/merlin/protocol/types/DurationTest.java index 79e60ab97a..30c140544a 100644 --- a/merlin-sdk/src/test/java/gov/nasa/jpl/aerie/merlin/protocol/types/DurationTest.java +++ b/merlin-sdk/src/test/java/gov/nasa/jpl/aerie/merlin/protocol/types/DurationTest.java @@ -10,6 +10,7 @@ import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.roundNearest; import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.roundUpward; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public final class DurationTest { @Test @@ -45,4 +46,34 @@ public void testSaturation() { assertEquals(Duration.MIN_VALUE.saturatingPlus(Duration.of(-1, SECONDS)), Duration.MIN_VALUE); assertEquals(Duration.MIN_VALUE.plus(Duration.SECOND).saturatingPlus(Duration.of(-2, SECONDS)), Duration.MIN_VALUE); } + + @Test + public void parseDurationFromString(){ + // Test positive duration + assertEquals(Duration.of(3, Duration.HOURS).plus(Duration.of(2, Duration.MINUTES).plus(Duration.of(3, Duration.SECONDS).plus(Duration.of(4, Duration.MICROSECONDS)))), + Duration.fromString("03:02:03.000004")); + // Test negative duration + assertEquals(Duration.of(-1, Duration.HOURS).plus(Duration.of(-2, Duration.MINUTES).plus(Duration.of(-3, Duration.SECONDS).plus(Duration.of(-4, Duration.MICROSECONDS)))), + Duration.fromString("-01:02:03.000004")); + // Test duration with no subseconds + assertEquals(Duration.of(1, Duration.HOURS).plus(Duration.of(2, Duration.MINUTES).plus(Duration.of(3, Duration.SECONDS))), + Duration.fromString("01:02:03")); + // Test duration with trailing zeros in subseconds + assertEquals(Duration.of(1, Duration.HOURS).plus(Duration.of(2, Duration.MINUTES).plus(Duration.of(3, Duration.SECONDS).plus(Duration.of(4, Duration.MICROSECONDS)))), + Duration.fromString("01:02:03.000004")); + // Test duration with leading hours + assertEquals(Duration.of(1234, Duration.HOURS).plus(Duration.of(12, Duration.MINUTES).plus(Duration.of(0, Duration.SECONDS).plus(Duration.of(123456, Duration.MICROSECONDS)))), + Duration.fromString("1234:12:00.123456")); + // Test unbalanced duration + assertEquals(Duration.of(1, Duration.HOURS).plus(Duration.of(0, Duration.MINUTES).plus(Duration.of(0, Duration.SECONDS))), + Duration.fromString("+00:60:00")); + + assertThrows(IllegalArgumentException.class, () -> Duration.fromString("+20:00")); + // invalid input + assertThrows(IllegalArgumentException.class,() -> Duration.fromString("3:2:03")); + // Test positive duration + assertThrows(IllegalArgumentException.class, () -> Duration.fromString("a1:023.1234567")); + assertThrows(IllegalArgumentException.class, () -> Duration.fromString("-01:02:03.12345678901")); + + } } diff --git a/merlin-server/build.gradle b/merlin-server/build.gradle index b9d145a482..8b8cb10212 100644 --- a/merlin-server/build.gradle +++ b/merlin-server/build.gradle @@ -79,6 +79,7 @@ application { } dependencies { + implementation project(':type-utils') implementation project(':merlin-driver') implementation project(':parsing-utilities') implementation project(':constraints') diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/HasuraParsers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/HasuraParsers.java index ef0ab785ca..6d556d7cfe 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/HasuraParsers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/HasuraParsers.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.http; import gov.nasa.jpl.aerie.json.JsonParser; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.server.models.HasuraAction; import gov.nasa.jpl.aerie.merlin.server.models.HasuraMissionModelEvent; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java index 9f82696a64..2558cd3278 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.constraints.InputMismatchException; import gov.nasa.jpl.aerie.json.JsonParser; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanDatasetException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; @@ -328,10 +328,10 @@ private void validatePlan(final Context ctx) { final var planId = parseJson(ctx.body(), hasuraPlanActionP).input().planId(); final var plan = this.planService.getPlanForValidation(planId); - final var activities = plan.activityDirectives.entrySet().stream().collect(Collectors.toMap( + final var activities = plan.activityDirectives().entrySet().stream().collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().serializedActivity())); - final var failures = this.missionModelService.validateActivityInstantiations(plan.missionModelId, activities); + final var failures = this.missionModelService.validateActivityInstantiations(plan.missionModelId(), activities); ctx.result(ResponseSerializers.serializeUnconstructableActivityFailures(failures).toString()); } catch (final InvalidJsonException ex) { diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsers.java index 3b0e903e3b..12501047cf 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsers.java @@ -3,15 +3,15 @@ import gov.nasa.jpl.aerie.json.JsonParseResult; import gov.nasa.jpl.aerie.json.JsonParser; import gov.nasa.jpl.aerie.json.SchemaCache; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.SimulationFailure; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.services.UnexpectedSubtypeError; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Timestamp; import javax.json.Json; import javax.json.JsonObject; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java index 8e16964305..a32f70e568 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java @@ -5,7 +5,6 @@ import gov.nasa.jpl.aerie.constraints.model.ConstraintResult; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.json.JsonParseResult.FailureReason; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.UnfinishedActivity; import gov.nasa.jpl.aerie.merlin.driver.json.ValueSchemaJsonParser; @@ -27,6 +26,7 @@ import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService.BulkEffectiveArgumentResponse; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService.BulkArgumentValidationResponse; import gov.nasa.jpl.aerie.merlin.server.services.UnexpectedSubtypeError; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.apache.commons.lang3.tuple.Pair; import javax.json.Json; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/mocks/InMemoryPlanRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/mocks/InMemoryPlanRepository.java index 8b58ae6518..56538dbecf 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/mocks/InMemoryPlanRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/mocks/InMemoryPlanRepository.java @@ -1,18 +1,18 @@ package gov.nasa.jpl.aerie.merlin.server.mocks; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.PlanRepository; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import java.util.HashMap; @@ -76,9 +76,7 @@ public InMemoryRevisionData getPlanRevisionData(final PlanId planId) throws NoSu public CreatedPlan storePlan(final Plan other) { final PlanId planId = new PlanId(this.nextPlanId++); final Plan plan = new Plan(other); - final List activityIds = - other.activityDirectives != null ? List.copyOf(plan.activityDirectives.keySet()) : List.of(); - if (other.activityDirectives == null) plan.activityDirectives = new HashMap<>(); + final List activityIds = List.copyOf(plan.activityDirectives().keySet()); this.plans.put(planId, Pair.of(0L, plan)); return new CreatedPlan(planId, activityIds); @@ -101,7 +99,7 @@ public ActivityDirectiveId createActivity(final PlanId planId, final ActivityDir final var revision = entry.getLeft() + 1; final ActivityDirectiveId activityId = new ActivityDirectiveId(this.nextActivityId++); - plan.activityDirectives.put(activityId, activity); + plan.activityDirectives().put(activityId, activity); this.plans.put(planId, Pair.of(revision, plan)); return activityId; @@ -114,7 +112,7 @@ public void deleteAllActivities(final PlanId planId) throws NoSuchPlanException final var plan = entry.getRight(); final var revision = entry.getLeft() + 1; - plan.activityDirectives.clear(); + plan.activityDirectives().clear(); this.plans.put(planId, Pair.of(revision, plan)); } diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveForValidation.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveForValidation.java index 6aefe8ca42..c293612460 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveForValidation.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveForValidation.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.models; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import java.sql.Timestamp; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveId.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveId.java deleted file mode 100644 index 09d741c365..0000000000 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ActivityDirectiveId.java +++ /dev/null @@ -1,3 +0,0 @@ -package gov.nasa.jpl.aerie.merlin.server.models; - -public record ActivityDirectiveId(long id) {} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraAction.java index 26a1f4fd17..1fcf8faba2 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraAction.java @@ -1,7 +1,9 @@ package gov.nasa.jpl.aerie.merlin.server.models; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.Timestamp; import java.util.List; import java.util.Map; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraMissionModelEvent.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraMissionModelEvent.java index 9ba33e4c5e..9932d2145c 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraMissionModelEvent.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/HasuraMissionModelEvent.java @@ -1,3 +1,5 @@ package gov.nasa.jpl.aerie.merlin.server.models; +import gov.nasa.jpl.aerie.types.MissionModelId; + public record HasuraMissionModelEvent(MissionModelId missionModelId) { } diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Plan.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Plan.java deleted file mode 100644 index 2406dd8b69..0000000000 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Plan.java +++ /dev/null @@ -1,107 +0,0 @@ -package gov.nasa.jpl.aerie.merlin.server.models; - -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public final class Plan { - public String name; - public MissionModelId missionModelId; - public Timestamp startTimestamp; - public Timestamp endTimestamp; - public Map activityDirectives; - public Map configuration = new HashMap<>(); - public Timestamp simulationStartTimestamp; - public Timestamp simulationEndTimestamp; - - public Plan() {} - - public Plan(final Plan other) { - this.name = other.name; - this.missionModelId = other.missionModelId; - this.startTimestamp = other.startTimestamp; - this.endTimestamp = other.endTimestamp; - this.simulationStartTimestamp = other.simulationStartTimestamp; - this.simulationEndTimestamp = other.simulationEndTimestamp; - - if (other.activityDirectives != null) { - this.activityDirectives = new HashMap<>(); - this.activityDirectives.putAll(other.activityDirectives); - } - - if (other.configuration != null) this.configuration = new HashMap<>(other.configuration); - } - - public Plan( - final String name, - final MissionModelId missionModelId, - final Timestamp startTimestamp, - final Timestamp endTimestamp, - final Map activityDirectives - ) { - this.name = name; - this.missionModelId = missionModelId; - this.startTimestamp = startTimestamp; - this.endTimestamp = endTimestamp; - this.activityDirectives = (activityDirectives != null) ? Map.copyOf(activityDirectives) : null; - this.configuration = null; - this.simulationStartTimestamp = startTimestamp; - this.simulationEndTimestamp = endTimestamp; - } - - public Plan( - final String name, - final MissionModelId missionModelId, - final Timestamp startTimestamp, - final Timestamp endTimestamp, - final Map activityDirectives, - final Map configuration, - final Timestamp simulationStartTimestamp, - final Timestamp simulationEndTimestamp - ) { - this.name = name; - this.missionModelId = missionModelId; - this.startTimestamp = startTimestamp; - this.endTimestamp = endTimestamp; - this.activityDirectives = (activityDirectives != null) ? Map.copyOf(activityDirectives) : null; - if (configuration != null) this.configuration = new HashMap<>(configuration); - this.simulationStartTimestamp = simulationStartTimestamp; - this.simulationEndTimestamp = simulationEndTimestamp; - } - - @Override - public boolean equals(final Object object) { - if (!(object instanceof final Plan other)) { - return false; - } - - return - (Objects.equals(this.name, other.name) - && Objects.equals(this.missionModelId, other.missionModelId) - && Objects.equals(this.startTimestamp, other.startTimestamp) - && Objects.equals(this.endTimestamp, other.endTimestamp) - && Objects.equals(this.activityDirectives, other.activityDirectives) - && Objects.equals(this.configuration, other.configuration) - && Objects.equals(this.simulationStartTimestamp, other.simulationStartTimestamp) - && Objects.equals(this.simulationEndTimestamp, other.simulationEndTimestamp) - ); - } - - @Override - public int hashCode() { - return Objects.hash( - name, - missionModelId, - startTimestamp, - endTimestamp, - activityDirectives, - configuration, - simulationStartTimestamp, - simulationEndTimestamp - ); - } -} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/MissionModelRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/MissionModelRepository.java index 9be6348e9e..cbe6e2d1b4 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/MissionModelRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/MissionModelRepository.java @@ -4,9 +4,9 @@ import gov.nasa.jpl.aerie.merlin.protocol.model.Resource; import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveForValidation; import gov.nasa.jpl.aerie.merlin.server.models.ActivityType; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.MissionModelJar; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService.BulkArgumentValidationResponse; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import java.util.List; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepository.java index 7eabd94ffe..97aaab5a5b 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepository.java @@ -1,18 +1,18 @@ package gov.nasa.jpl.aerie.merlin.server.remotes; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanDatasetException; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.services.RevisionData; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import java.util.List; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreatePlanDatasetAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreatePlanDatasetAction.java index 2a8fbd3e73..76251987ad 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreatePlanDatasetAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreatePlanDatasetAction.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreateSimulationDatasetAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreateSimulationDatasetAction.java index 88003d00b3..aae2ea5f7f 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreateSimulationDatasetAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/CreateSimulationDatasetAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.postgres.SimulationStateRecord.Status; import org.intellij.lang.annotations.Language; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetAllPlansAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetAllPlansAction.java index b3d7ebd123..5a5d6a129b 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetAllPlansAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetAllPlansAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetPlanAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetPlanAction.java index 900908249e..aa9213ca2a 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetPlanAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetPlanAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationAction.java index 1bc4a828ea..726e48923f 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetAction.java index f5071d8ba4..da16519746 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.postgres.SimulationStateRecord.Status; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetByIdAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetByIdAction.java index cd32f9f5e1..90c527fa8e 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetByIdAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSimulationDatasetByIdAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSpanRecords.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSpanRecords.java index 6b31e9821e..5b35e293c8 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSpanRecords.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetSpanRecords.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetUnvalidatedDirectivesAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetUnvalidatedDirectivesAction.java index 2f69e85c46..8971e8caf4 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetUnvalidatedDirectivesAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetUnvalidatedDirectivesAction.java @@ -1,9 +1,9 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveForValidation; -import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import java.sql.Connection; @@ -26,7 +26,7 @@ public class GetUnvalidatedDirectivesAction implements AutoCloseable { join merlin.plan p on ad.plan_id = p.id where adv.status = 'pending'; - """; + """; private final PreparedStatement statement; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/InsertSimulationEventsAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/InsertSimulationEventsAction.java index 8ad0f1eb13..07bcdc8f0c 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/InsertSimulationEventsAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/InsertSimulationEventsAction.java @@ -3,7 +3,7 @@ import gov.nasa.jpl.aerie.merlin.driver.engine.EventRecord; import gov.nasa.jpl.aerie.merlin.driver.timeline.EventGraph; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import org.intellij.lang.annotations.Language; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/LookupSimulationDatasetAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/LookupSimulationDatasetAction.java index 6faf4162d0..fe63058a59 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/LookupSimulationDatasetAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/LookupSimulationDatasetAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.postgres.SimulationStateRecord.Status; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PlanRecord.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PlanRecord.java index 1da8c516c5..b7e7588ebf 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PlanRecord.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PlanRecord.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; public record PlanRecord( long id, diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostSpansAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostSpansAction.java index 11ec124c47..5c2307379a 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostSpansAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostSpansAction.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import java.sql.Connection; @@ -11,7 +11,6 @@ import java.sql.Statement; import java.sql.Types; import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.Map; import java.util.Optional; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresMissionModelRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresMissionModelRepository.java index f2e9d9ffda..0d97d8ec82 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresMissionModelRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresMissionModelRepository.java @@ -4,10 +4,10 @@ import gov.nasa.jpl.aerie.merlin.protocol.model.Resource; import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveForValidation; import gov.nasa.jpl.aerie.merlin.server.models.ActivityType; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.MissionModelJar; import gov.nasa.jpl.aerie.merlin.server.remotes.MissionModelRepository; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import javax.sql.DataSource; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsers.java index bad5dc32fb..87cf3d3057 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsers.java @@ -7,8 +7,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.services.UnexpectedSubtypeError; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import org.postgresql.util.PGInterval; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresPlanRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresPlanRepository.java index 9c7cf1862c..10a496fe4f 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresPlanRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresPlanRepository.java @@ -1,7 +1,5 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; @@ -9,13 +7,15 @@ import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.PlanRepository; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import javax.sql.DataSource; @@ -278,7 +278,7 @@ public List> getExternalDatasets( } @Override - public Map getExternalResourceSchemas(final PlanId planId, final Optional simulationDatasetId) throws NoSuchPlanException { + public Map getExternalResourceSchemas(final PlanId planId, final Optional simulationDatasetId) throws DatabaseException { try (final var connection = this.dataSource.getConnection()) { final var planDatasets = ProfileRepository.getPlanDatasetsForPlan(connection, planId, simulationDatasetId); final var result = new HashMap(); diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresResultsCellRepository.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresResultsCellRepository.java index e6d1c54b6c..39b7cd2f5e 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresResultsCellRepository.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.driver.SimulationException; @@ -19,8 +18,9 @@ import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; import gov.nasa.jpl.aerie.merlin.server.models.SimulationResultsHandle; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.ResultsCellRepository; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; @@ -407,9 +407,7 @@ private static void postActivities( final Map unfinishedActivities, final Timestamp simulationStart ) throws SQLException { - try ( - final var postActivitiesAction = new PostSpansAction(connection); - ) { + try (final var postActivitiesAction = new PostSpansAction(connection)) { final var simulatedActivityRecords = simulatedActivities.entrySet().stream() .collect(Collectors.toMap( e -> e.getKey().id(), diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PreparedStatements.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PreparedStatements.java index 639561e6f5..135704b712 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PreparedStatements.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PreparedStatements.java @@ -5,8 +5,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.server.http.MerlinParsers; import gov.nasa.jpl.aerie.merlin.server.http.ResponseSerializers; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import javax.json.Json; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationDatasetRecord.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationDatasetRecord.java index 6bdc107ebe..3c1f6d4158 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationDatasetRecord.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationDatasetRecord.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; + +import gov.nasa.jpl.aerie.types.Timestamp; public record SimulationDatasetRecord( long simulationId, diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationRecord.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationRecord.java index e209d44b86..a8a87babf9 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationRecord.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/SimulationRecord.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.merlin.server.remotes.postgres; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import java.util.Map; import java.util.Optional; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java index 38105f4684..3b16377768 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintAction.java @@ -71,15 +71,11 @@ public Map> getViolations(final PlanId planId, final Opt if (!constraintCode.isEmpty()) { final var simStartTime = resultsHandle$ .map(gov.nasa.jpl.aerie.merlin.server.models.SimulationResultsHandle::startTime) - .orElse(plan.startTimestamp.toInstant()); + .orElse(plan.simulationStartInstant()); final var simDuration = resultsHandle$ .map(SimulationResultsHandle::duration) - .orElse(Duration.of( - plan.startTimestamp.toInstant().until(plan.endTimestamp.toInstant(), ChronoUnit.MICROS), - Duration.MICROSECONDS)); - final var simOffset = Duration.of( - plan.startTimestamp.toInstant().until(simStartTime, ChronoUnit.MICROS), - Duration.MICROSECONDS); + .orElse(plan.simulationDuration()); + final var simOffset = plan.simulationOffset(); final var activities = new ArrayList(); final var simulatedActivities = resultsHandle$ @@ -138,7 +134,7 @@ public Map> getViolations(final PlanId planId, final Opt final ConstraintsDSLCompilationService.ConstraintsDSLCompilationResult constraintCompilationResult; try { constraintCompilationResult = constraintsDSLCompilationService.compileConstraintsDSL( - plan.missionModelId, + plan.missionModelId(), Optional.of(planId), Optional.of(simDatasetId), constraint.definition() diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationService.java index f99a0f440c..2070154031 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationService.java @@ -8,9 +8,9 @@ import gov.nasa.jpl.aerie.merlin.server.http.InvalidJsonException; import gov.nasa.jpl.aerie.merlin.server.models.ConstraintsCompilationError; import gov.nasa.jpl.aerie.constraints.json.ConstraintParsers; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; +import gov.nasa.jpl.aerie.types.MissionModelId; import javax.json.Json; import javax.json.JsonObject; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/CreateSimulationMessage.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/CreateSimulationMessage.java deleted file mode 100644 index ac6697fe58..0000000000 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/CreateSimulationMessage.java +++ /dev/null @@ -1,32 +0,0 @@ -package gov.nasa.jpl.aerie.merlin.server.services; - -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; -import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; - -import java.time.Instant; -import java.util.Map; -import java.util.Objects; - -public record CreateSimulationMessage( - MissionModelId missionModelId, - Instant simulationStartTime, - Duration simulationDuration, - Instant planStartTime, - Duration planDuration, - Map activityDirectives, - Map configuration -) -{ - public CreateSimulationMessage { - Objects.requireNonNull(missionModelId); - Objects.requireNonNull(simulationStartTime); - Objects.requireNonNull(simulationDuration); - Objects.requireNonNull(planStartTime); - Objects.requireNonNull(planDuration); - Objects.requireNonNull(activityDirectives); - Objects.requireNonNull(configuration); - } -} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/GenerateConstraintsLibAction.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/GenerateConstraintsLibAction.java index 7210b5f1c6..0eb357875d 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/GenerateConstraintsLibAction.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/GenerateConstraintsLibAction.java @@ -1,8 +1,8 @@ package gov.nasa.jpl.aerie.merlin.server.services; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; +import gov.nasa.jpl.aerie.types.MissionModelId; import java.io.IOException; import java.nio.file.Files; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java index 538fe398de..f2d4fa4658 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java @@ -1,13 +1,14 @@ package gov.nasa.jpl.aerie.merlin.server.services; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.SimulationDriver; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; -import gov.nasa.jpl.aerie.merlin.driver.engine.ProfileSegment; import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.Parameter; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.ValidationNotice; @@ -18,7 +19,6 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveForValidation; import gov.nasa.jpl.aerie.merlin.server.models.ActivityType; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.MissionModelJar; import gov.nasa.jpl.aerie.merlin.server.remotes.MissionModelRepository; import org.apache.commons.lang3.tuple.Pair; @@ -280,19 +280,19 @@ public Map getModelEffectiveArguments(final MissionMode /** * Validate that a set of activity parameters conforms to the expectations of a named mission model. * - * @param message The parameters defining the simulation to perform. + * @param plan The plan to be simulated. Contains the parameters defining the simulation to perform. * @return A set of samples over the course of the simulation. * @throws NoSuchMissionModelException If no mission model is known by the given ID. */ @Override public SimulationResults runSimulation( - final CreateSimulationMessage message, + final Plan plan, final Consumer simulationExtentConsumer, final Supplier canceledListener, final SimulationResourceManager resourceManager) throws NoSuchMissionModelException { - final var config = message.configuration(); + final var config = plan.simulationConfiguration(); if (config.isEmpty()) { log.warn( "No mission model configuration defined for mission model. Simulations will receive an empty set of configuration arguments."); @@ -301,14 +301,14 @@ public SimulationResults runSimulation( // TODO: [AERIE-1516] Teardown the mission model after use to release any system resources (e.g. threads). return SimulationDriver.simulate( loadAndInstantiateMissionModel( - message.missionModelId(), - message.simulationStartTime(), + plan.missionModelId(), + plan.planStartInstant(), SerializedValue.of(config)), - message.activityDirectives(), - message.simulationStartTime(), - message.simulationDuration(), - message.planStartTime(), - message.planDuration(), + plan.activityDirectives(), + plan.simulationStartInstant(), + plan.simulationDuration(), + plan.planStartInstant(), + plan.duration(), canceledListener, simulationExtentConsumer, resourceManager); diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalPlanService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalPlanService.java index e12c366e67..93bdf89134 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalPlanService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalPlanService.java @@ -6,12 +6,12 @@ import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.remotes.PlanRepository; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import java.util.List; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/MissionModelService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/MissionModelService.java index 673c053b75..cb3bda30e5 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/MissionModelService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/MissionModelService.java @@ -1,10 +1,11 @@ package gov.nasa.jpl.aerie.merlin.server.services; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; -import gov.nasa.jpl.aerie.merlin.driver.engine.ProfileSegment; import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.Parameter; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.ValidationNotice; @@ -13,10 +14,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.models.ActivityType; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.MissionModelJar; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -66,7 +65,7 @@ Map getModelEffectiveArguments(MissionModelId missionMo InstantiationException; SimulationResults runSimulation( - final CreateSimulationMessage message, + final Plan plan, final Consumer writer, final Supplier canceledListener, final SimulationResourceManager resourceManager diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/PlanService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/PlanService.java index f70ce35c35..4bd652b3fe 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/PlanService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/PlanService.java @@ -6,11 +6,11 @@ import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import java.util.List; diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/SimulationAgent.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/SimulationAgent.java index b999b3e541..96556b223b 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/SimulationAgent.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/SimulationAgent.java @@ -7,11 +7,10 @@ import gov.nasa.jpl.aerie.merlin.server.ResultsProtocol; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.http.ResponseSerializers; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; +import gov.nasa.jpl.aerie.types.Plan; import javax.json.Json; -import java.time.temporal.ChronoUnit; import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -50,19 +49,12 @@ public void simulate( return; } - final var planDuration = Duration.of( - plan.startTimestamp.toInstant().until(plan.endTimestamp.toInstant(), ChronoUnit.MICROS), - Duration.MICROSECONDS); - final var simDuration = Duration.of( - plan.simulationStartTimestamp.toInstant().until(plan.simulationEndTimestamp.toInstant(), ChronoUnit.MICROS), - Duration.MICROSECONDS); - final SimulationResults results; try { // Validate plan activity construction final var failures = this.missionModelService.validateActivityInstantiations( - plan.missionModelId, - plan.activityDirectives.entrySet().stream().collect(Collectors.toMap( + plan.missionModelId(), + plan.activityDirectives().entrySet().stream().collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().serializedActivity()))); @@ -80,14 +72,7 @@ public void simulate( simulationProgressPollPeriod) ) { results = this.missionModelService.runSimulation( - new CreateSimulationMessage( - plan.missionModelId, - plan.simulationStartTimestamp.toInstant(), - simDuration, - plan.startTimestamp.toInstant(), - planDuration, - plan.activityDirectives, - plan.configuration), + plan, extentListener::updateValue, canceledListener, resourceManager); diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceAdapter.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceAdapter.java index 18bc66d273..98c02dfb80 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceAdapter.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceAdapter.java @@ -4,9 +4,9 @@ import gov.nasa.jpl.aerie.constraints.TypescriptCodeGenerationService; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; +import gov.nasa.jpl.aerie.types.MissionModelId; import java.util.HashMap; import java.util.Map; diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsersTest.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsersTest.java index 3c09fdb901..c2c594f04a 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsersTest.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinParsersTest.java @@ -4,7 +4,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.server.models.HasuraAction; import gov.nasa.jpl.aerie.merlin.server.models.HasuraMissionModelEvent; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.Test; import javax.json.Json; diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubMissionModelService.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubMissionModelService.java index 93f28b5a9e..391dea2529 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubMissionModelService.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubMissionModelService.java @@ -1,7 +1,9 @@ package gov.nasa.jpl.aerie.merlin.server.mocks; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.Parameter; @@ -10,9 +12,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.models.ActivityType; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.MissionModelJar; -import gov.nasa.jpl.aerie.merlin.server.services.CreateSimulationMessage; import gov.nasa.jpl.aerie.merlin.server.services.LocalMissionModelService; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService; @@ -195,24 +195,24 @@ public Map getModelEffectiveArguments( @Override public SimulationResults runSimulation( - final CreateSimulationMessage message, + final Plan plan, final Consumer simulationExtentConsumer, final Supplier canceledListener, final SimulationResourceManager resourceManager ) throws NoSuchMissionModelException { - if (!Objects.equals(message.missionModelId(), EXISTENT_MISSION_MODEL_ID)) { - throw new NoSuchMissionModelException(message.missionModelId()); + if (!Objects.equals(plan.missionModelId(), EXISTENT_MISSION_MODEL_ID)) { + throw new NoSuchMissionModelException(plan.missionModelId()); } return SUCCESSFUL_SIMULATION_RESULTS; } @Override - public void refreshModelParameters(final MissionModelId missionModelId) throws NoSuchMissionModelException {} + public void refreshModelParameters(final MissionModelId missionModelId) {} @Override - public void refreshActivityTypes(final MissionModelId missionModelId) throws NoSuchMissionModelException {} + public void refreshActivityTypes(final MissionModelId missionModelId) {} @Override - public void refreshResourceTypes(final MissionModelId missionModelId) throws NoSuchMissionModelException {} + public void refreshResourceTypes(final MissionModelId missionModelId) {} } diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubPlanService.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubPlanService.java index 8026660c38..2fd44de131 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubPlanService.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/mocks/StubPlanService.java @@ -1,23 +1,24 @@ package gov.nasa.jpl.aerie.merlin.server.mocks; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.models.Constraint; import gov.nasa.jpl.aerie.merlin.server.models.DatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.models.ProfileSet; import gov.nasa.jpl.aerie.merlin.server.models.SimulationDatasetId; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; import gov.nasa.jpl.aerie.merlin.server.services.PlanService; import gov.nasa.jpl.aerie.merlin.server.services.RevisionData; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Objects; @@ -46,11 +47,7 @@ public MatchResult matches(final RevisionData other) { null, true ); - - EXISTENT_PLAN = new Plan(); - EXISTENT_PLAN.name = "existent"; - EXISTENT_PLAN.missionModelId = new MissionModelId(1); - EXISTENT_PLAN.activityDirectives = Map.of(EXISTENT_ACTIVITY_ID, EXISTENT_ACTIVITY); + EXISTENT_PLAN = new Plan("existent", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of(EXISTENT_ACTIVITY_ID, EXISTENT_ACTIVITY)); } @@ -81,7 +78,7 @@ public RevisionData getPlanRevisionData(final PlanId planId) throws NoSuchPlanEx @Override public Map getConstraintsForPlan(final PlanId planId) - { + throws NoSuchPlanException { return Map.of(); } @@ -91,6 +88,7 @@ public long addExternalDataset( final Optional simulationDatasetId, final Timestamp datasetStart, final ProfileSet profileSet) + throws NoSuchPlanException { return 0; } @@ -104,13 +102,13 @@ public void extendExternalDataset(final DatasetId datasetId, final ProfileSet pr public List> getExternalDatasets( final PlanId planId, final SimulationDatasetId simulationDatasetId - ) + ) throws NoSuchPlanException { return List.of(); } @Override - public Map getExternalResourceSchemas(final PlanId planId, final Optional simulationDatasetId) { + public Map getExternalResourceSchemas(final PlanId planId, final Optional simulationDatasetId) throws NoSuchPlanException { return Map.of("external resource", ValueSchema.BOOLEAN); } diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelTest.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelTest.java index d13b4532b3..3a03e9cdbc 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelTest.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelTest.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.foomissionmodel.generated.GeneratedModelType; import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; +import gov.nasa.jpl.aerie.types.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.Parameter; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepositoryContractTest.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepositoryContractTest.java index 9404358dd8..54718d4b5c 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepositoryContractTest.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/PlanRepositoryContractTest.java @@ -4,12 +4,15 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.mocks.InMemoryPlanRepository; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.server.models.Plan; import gov.nasa.jpl.aerie.merlin.server.remotes.PlanRepository.CreatedPlan; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.time.Instant; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,14 +34,13 @@ public void testCanStorePlan() throws NoSuchPlanException { // GIVEN // WHEN - final Plan plan = new Plan(); - plan.name = "new-plan"; + final Plan plan = new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of()); final CreatedPlan ids = this.planRepository.storePlan(plan); // THEN final Plan fetchedPlan = this.planRepository.getPlanForValidation(ids.planId()); - assertEquals("new-plan", fetchedPlan.name); + assertEquals("new-plan", fetchedPlan.name()); } @Test @@ -48,18 +50,16 @@ public void testCreatePlanWithActivity() throws NoSuchPlanException { // WHEN final ActivityDirective activity = new ActivityDirective(Duration.ZERO, "abc", Map.of("abc", SerializedValue.of(1)), null, true); - final Plan plan = new Plan(); - plan.name = "new-plan"; - plan.activityDirectives = Map.of(); + final Plan plan = new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of()); final CreatedPlan ids = this.planRepository.storePlan(plan); this.planRepository.createActivity(ids.planId(), activity); // THEN final Plan fetchedPlan = this.planRepository.getPlanForValidation(ids.planId()); - assertEquals("new-plan", fetchedPlan.name); - assertEquals(1, fetchedPlan.activityDirectives.size()); - assertTrue(fetchedPlan.activityDirectives.containsValue(activity)); + assertEquals("new-plan", fetchedPlan.name()); + assertEquals(1, fetchedPlan.activityDirectives().size()); + assertTrue(fetchedPlan.activityDirectives().containsValue(activity)); } @Test @@ -69,10 +69,10 @@ public void testCreatePlanWithNullActivitiesList() // GIVEN // WHEN - final CreatedPlan ids = this.planRepository.storePlan(new Plan()); + final CreatedPlan ids = this.planRepository.storePlan(new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of())); // THEN - final var directives = this.planRepository.getPlanForValidation(ids.planId()).activityDirectives; + final var directives = this.planRepository.getPlanForValidation(ids.planId()).activityDirectives(); assertNotNull(directives); assertTrue(directives.isEmpty()); } @@ -80,9 +80,9 @@ public void testCreatePlanWithNullActivitiesList() @Test public void testCanDeletePlan() throws NoSuchPlanException { // GIVEN - this.planRepository.storePlan(new Plan()); - final CreatedPlan ids = this.planRepository.storePlan(new Plan()); - this.planRepository.storePlan(new Plan()); + this.planRepository.storePlan(new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of())); + final CreatedPlan ids = this.planRepository.storePlan(new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of())); + this.planRepository.storePlan(new Plan("new-plan", new MissionModelId(1), new Timestamp(Instant.now()), new Timestamp(Instant.now()), Map.of())); assertEquals(3, this.planRepository.getAllPlans().size()); // WHEN diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsersTest.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsersTest.java index 08cde20175..aabbeb4cdb 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsersTest.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/PostgresParsersTest.java @@ -4,7 +4,7 @@ import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.pgTimestampP; import static org.junit.jupiter.api.Assertions.assertEquals; -import gov.nasa.jpl.aerie.merlin.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.junit.jupiter.api.Test; public final class PostgresParsersTest { diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java index 8b0276f15c..e4ae572a8d 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java @@ -52,8 +52,8 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.server.mocks.StubMissionModelService; import gov.nasa.jpl.aerie.merlin.server.mocks.StubPlanService; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceTest.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceTest.java index 33cfdaae2f..660d0cf143 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceTest.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/TypescriptCodeGenerationServiceTest.java @@ -3,8 +3,8 @@ import gov.nasa.jpl.aerie.merlin.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.merlin.server.mocks.StubMissionModelService; import gov.nasa.jpl.aerie.merlin.server.mocks.StubPlanService; -import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId; import gov.nasa.jpl.aerie.merlin.server.models.PlanId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/orchestration-utils/build.gradle b/orchestration-utils/build.gradle new file mode 100644 index 0000000000..78f1a41230 --- /dev/null +++ b/orchestration-utils/build.gradle @@ -0,0 +1,74 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'jacoco' + id 'application' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + + withJavadocJar() + withSourcesJar() +} + +test { + useJUnitPlatform { + includeEngines 'junit-jupiter' + } + testLogging { + exceptionFormat = 'full' + } + // Allow security manager so it can be overridden with mock for unit tests + systemProperty("java.security.manager", "allow") +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } +} + + +dependencies { + implementation project(':merlin-server') + implementation project(':merlin-driver') + implementation project(':parsing-utilities') + implementation project(':type-utils') + implementation 'commons-cli:commons-cli:1.8.0' + + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + + +// Link references to standard Java classes to the official Java 11 documentation. +javadoc.options.links 'https://docs.oracle.com/en/java/javase/11/docs/api/' +javadoc.options.links 'https://commons.apache.org/proper/commons-lang/javadocs/api-3.9/' +javadoc.options.addStringOption('Xdoclint:none', '-quiet') + + +publishing { + publications { + library(MavenPublication) { + version = findProperty('publishing.version') + from components.java + } + } + + publishing { + repositories { + maven { + name = findProperty("publishing.name") + url = findProperty("publishing.url") + credentials { + username = System.getenv(findProperty("publishing.usernameEnvironmentVariable")) + password = System.getenv(findProperty("publishing.passwordEnvironmentVariable")) + } + } + } + } +} diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/PlanJsonParser.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/PlanJsonParser.java new file mode 100644 index 0000000000..25c1bb3158 --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/PlanJsonParser.java @@ -0,0 +1,138 @@ +package gov.nasa.jpl.aerie.orchestration; + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; + +import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.activityArgumentsP; +import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.pgTimestampP; +import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.simulationArgumentsP; + +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; + +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +/** + * Class to parse a plan.json file. + */ +public class PlanJsonParser { + + private PlanJsonParser() {} + + /** + * Parses a plan.json file that has been exported by the Aerie Gateway. + */ + public static Plan parsePlan(final Path filePath) { + try (final var fileReader = new FileReader(filePath.toString())) { + final var parser = Json.createParser(fileReader); + parser.next(); + final var planObject = parser.getObject(); + + final var name = planObject.getString("name"); + final var duration = Duration.fromString(planObject.getString("duration")); + final Timestamp startTime = pgTimestampP.parse(planObject.get("start_time")).getSuccessOrThrow(); + final Timestamp endTime = startTime.plusMicros(duration.in(Duration.MICROSECOND)); + + final var activityDirectives = parseActivities(planObject.getJsonArray("activities")); + final var simulationConfig = parseSimulationConfiguration(planObject.getJsonObject("simulation_arguments")); + + return new Plan(name, startTime, endTime, activityDirectives, simulationConfig); + } catch (final FileNotFoundException e) { + throw new RuntimeException("Specified plan JSON file does not exist: " + filePath); + } catch (final Exception e) { + throw new RuntimeException("Error while reading plan JSON file: " + filePath, e); + } + } + + /** + * Parse an array of JSON activities into a map of ActivityDirectives + * + * @param activities the json array directives to be parsed + */ + private static Map parseActivities(final JsonArray activities) { + final var activitiesMap = new HashMap(activities.size()); + + activities.forEach(v -> { + final var a = v.asJsonObject(); + + final var id = new ActivityDirectiveId(a.getInt("id")); + final var startOffset = Duration.fromString(a.getString("start_offset")); + final var type = a.getString("type"); + final var anchoredToStart = a.getBoolean("anchored_to_start"); + final var anchorId = a.isNull("anchor_id") ? null : new ActivityDirectiveId(a.getInt("anchor_id")); + final var arguments = activityArgumentsP.parse(a.getJsonObject("arguments")).getSuccessOrThrow(); + + activitiesMap.put( + id, + new ActivityDirective( + startOffset, + type, + arguments, + anchorId, + anchoredToStart + )); + }); + + return activitiesMap; + } + + /** + * Parses the simulation configuration from a jsonObject into a Map + * + * @param simConfig the JsonObject containing the simulation configuration + * @return A map containing the parsed simulation configuration + **/ + private static Map parseSimulationConfiguration(final JsonObject simConfig) { + // Return if we don't have any simConfigs + if (simConfig.isEmpty()) + return Map.of(); + return simulationArgumentsP.parse(simConfig).getSuccessOrThrow(); + } + + + /** + * Parses a Simulation Configuration JSON file and updates the given plan accordingly. + * + * Schema for a Simulation Configuration: + * { + * version: "2" + * simulation_start_time: string (PG Timestamp) + * simulation_end_time: string (PG Timestamp) + * arguments: json object + * } + * + * @param filePath path to the config file + * @param plan plan object to be updated + */ + public static void parseSimulationConfiguration(final Path filePath, final Plan plan) { + try (final var fileReader = new FileReader(filePath.toString())) { + final var parser = Json.createParser(fileReader); + parser.next(); + final var configObject = parser.getObject(); + + final var simStartTime = pgTimestampP.parse(configObject.get("simulation_start_time")).getSuccessOrThrow(); + final var simEndTime = pgTimestampP.parse(configObject.get("simulation_end_time")).getSuccessOrThrow(); + final var config = PlanJsonParser.parseSimulationConfiguration(configObject.getJsonObject("arguments")); + + plan.simulationConfiguration().putAll(config); + plan.simulationStartTimestamp = simStartTime; + plan.simulationEndTimestamp = simEndTime; + + } catch (final FileNotFoundException e) { + throw new RuntimeException("Specified simulation configuration JSON file does not exist: " + filePath); + } catch (final Exception e) { + throw new RuntimeException("Error while reading simulation configuration JSON file: " + filePath, e); + } + } +} diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/CanceledListener.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/CanceledListener.java new file mode 100644 index 0000000000..72bada00a6 --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/CanceledListener.java @@ -0,0 +1,15 @@ +package gov.nasa.jpl.aerie.orchestration.simulation; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +public class CanceledListener implements Supplier { + private final AtomicBoolean canceled = new AtomicBoolean(false); + + public void cancel() { canceled.set(true); } + + @Override + public Boolean get() { + return canceled.get(); + } +} diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/ResourceFileStreamer.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/ResourceFileStreamer.java new file mode 100644 index 0000000000..3c939079ee --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/ResourceFileStreamer.java @@ -0,0 +1,102 @@ +package gov.nasa.jpl.aerie.orchestration.simulation; + +import gov.nasa.jpl.aerie.merlin.driver.resources.ResourceProfiles; + +import javax.json.Json; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.UUID; +import java.util.function.Consumer; + +import static gov.nasa.jpl.aerie.merlin.driver.json.SerializedValueJsonParser.serializedValueP; +import static gov.nasa.jpl.aerie.merlin.server.http.ProfileParsers.realDynamicsP; + +/** + * A consumer that writes resource segments to the file system. + */ +public class ResourceFileStreamer implements Consumer { + private final UUID uuid; + private final HashMap fileNames; + + public ResourceFileStreamer() { + uuid = UUID.randomUUID(); + fileNames = new HashMap<>(); + } + + /* + Forbidden Characters for File Names: + Assuming no nonprintable characters are used, as resource names are already visualized in the UI + + Forbidden on Windows (Linux and Mac use a subset): + < (less than) + > (greater than) + : (colon - sometimes works, but is actually NTFS Alternate Data Streams) + " (double quote) + / (forward slash) + \ (backslash) + | (vertical bar or pipe) + ? (question mark) + * (asterisk) + + Forbidden for Being Potentially Problematic: + . (period) (windows doesn't allow trailing '.', file extension signifier) + , (comma) + + (plus) + & (ampersand) + ' (single quote) + ' ' (space) + */ + private static final String[] EXCLUSION = {"<", ">", ",", ":", "\"", "\\\\", "/", "|", "?", "*", ".","+", "&", "'"," "}; + + @Override + public void accept(final ResourceProfiles resourceProfile) { + for(final var r : resourceProfile.realProfiles().entrySet()) { + final var name = getFileName(r.getKey()); + try (final var fileWriter = new FileWriter(name, true)) { + for(final var segment : r.getValue().segments()) { + final var s = Json.createObjectBuilder() + .add("extent", segment.extent().toString()) + .add("dynamics", realDynamicsP.unparse(segment.dynamics())) + .build(); + fileWriter.write(s.toString()+"\n"); + } + fileWriter.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + for(final var d : resourceProfile.discreteProfiles().entrySet()) { + final var name = getFileName(d.getKey()); + try (final var fileWriter = new FileWriter(name, true)) { + for(final var segment : d.getValue().segments()) { + final var s = Json.createObjectBuilder() + .add("extent", segment.extent().toString()) + .add("dynamics", serializedValueP.unparse(segment.dynamics())) + .build(); + fileWriter.write(s.toString()+"\n"); + } + fileWriter.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Converts a resource's name into a legal file name and saves it in its cache of filenames. + */ + public String getFileName(String resourceName) { + if(fileNames.containsKey(resourceName)) return fileNames.get(resourceName); + // Create files in the temp directory, or the PWD if there is no set tmpdir + String dirname = System.getProperty("java.io.tmpdir", "."); + if(!dirname.endsWith("/")) dirname = dirname + "/"; // Append a Path deliminator if necessary + + final var fileName = dirname + resourceName.replaceAll("[" + Arrays.toString(EXCLUSION) + "]", "_") + uuid.toString()+".rsc"; + fileNames.put(resourceName, fileName); + return fileName; + } + +} diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationExtentConsumer.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationExtentConsumer.java new file mode 100644 index 0000000000..b997b1444d --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationExtentConsumer.java @@ -0,0 +1,50 @@ +package gov.nasa.jpl.aerie.orchestration.simulation; + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.function.Consumer; + +public class SimulationExtentConsumer implements Consumer, AutoCloseable { + private Duration lastAcceptedDuration = Duration.ZERO; + private Duration lastReportedDuration = null; + private final Timer printTimer; + + /** + * Create a new SimulationExtentConsumer that prints the simulation time with a minimum frequency. + * @param periodMillis The minimum gap between simulation extent updates, in milliseconds. + */ + public SimulationExtentConsumer(final long periodMillis) { + printTimer = new Timer(); + printTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // Only print if simulation time has progressed. + if(!lastAcceptedDuration.isEqualTo(lastReportedDuration)) { + System.out.println("Current simulation time: " + lastAcceptedDuration); + lastReportedDuration = lastAcceptedDuration; + } + } + }, periodMillis, periodMillis); + } + + /** + * Create a new SimulationExtentConsumer that consumes but does not print the simulation time. + */ + public SimulationExtentConsumer() { + printTimer = new Timer(); + } + + @Override + public void accept(final Duration duration) { + lastAcceptedDuration = duration; + } + + public Duration getLastAcceptedDuration() { return lastAcceptedDuration; } + + @Override + public void close() { + printTimer.cancel(); + } +} diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationResultsWriter.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationResultsWriter.java new file mode 100644 index 0000000000..9b4fab105b --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationResultsWriter.java @@ -0,0 +1,586 @@ +package gov.nasa.jpl.aerie.orchestration.simulation; + +import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; +import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; +import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; +import gov.nasa.jpl.aerie.merlin.driver.UnfinishedActivity; +import gov.nasa.jpl.aerie.merlin.driver.engine.EventRecord; +import gov.nasa.jpl.aerie.merlin.driver.resources.ResourceProfile; +import gov.nasa.jpl.aerie.merlin.driver.timeline.EventGraph; +import gov.nasa.jpl.aerie.merlin.protocol.types.RealDynamics; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonValue; +import javax.json.stream.JsonGenerator; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.concurrent.RecursiveTask; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.server.remotes.postgres.EventGraphFlattener; +import gov.nasa.jpl.aerie.types.Plan; +import gov.nasa.jpl.aerie.types.Timestamp; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; + +import static gov.nasa.jpl.aerie.merlin.driver.json.SerializedValueJsonParser.serializedValueP; +import static gov.nasa.jpl.aerie.merlin.driver.json.ValueSchemaJsonParser.valueSchemaP; +import static gov.nasa.jpl.aerie.merlin.server.http.ProfileParsers.realDynamicsP; +import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.activityArgumentsP; +import static gov.nasa.jpl.aerie.merlin.server.remotes.postgres.PostgresParsers.simulationArgumentsP; + + +public class SimulationResultsWriter { + private final static double SCHEMA_VERSION = 1; + + // Write JSONs with Pretty Printing + private final static Map config = Map.of(JsonGenerator.PRETTY_PRINTING, ""); + + private final RecursiveTask profilesTask; + private final RecursiveTask eventsTask; + private final RecursiveTask spansTask; + private final RecursiveTask simConfigTask; + + private final Plan plan; + private final Duration extent; + + /** + * Creates a SimulationResultsWriter that will write SimulationResults generated + * using a StreamingResourceManager using the provided ResourceFileStreamer. + * @param results The SimulationResults to be written + * @param plan The Plan simulated + * @param rfs The ResourceFileStreamer used during the simulation + */ + public SimulationResultsWriter(SimulationResults results, Plan plan, ResourceFileStreamer rfs) { + this.plan = plan; + this.extent = results.duration; + this.profilesTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + try { + return buildProfiles(results.realProfiles, results.discreteProfiles, rfs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + this.eventsTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildEvents(results.events,results.topics); + } + }; + this.spansTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildSpans(results.simulatedActivities,results.unfinishedActivities, plan.simulationStartTimestamp); + } + }; + this.simConfigTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildSimConfig(plan); + } + }; + } + + /** + * Create a SimulationResultsWriter that will write SimulationResults generated + * using an InMemorySimulationResourceManager. + * @param results The SimulationResults to be written + * @param plan The plan simulated + */ + public SimulationResultsWriter(SimulationResults results, Plan plan) { + this.plan = plan; + this.extent = results.duration; + this.profilesTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildProfiles(results.realProfiles, results.discreteProfiles); + } + }; + this.eventsTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildEvents(results.events,results.topics); + } + }; + this.spansTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildSpans(results.simulatedActivities,results.unfinishedActivities, plan.simulationStartTimestamp); + } + }; + this.simConfigTask = new RecursiveTask<>() { + @Override + protected JsonObject compute() { + return buildSimConfig(plan); + } + }; + } + + /** Fork tasks to build subjsons in parallel **/ + private void forkSubTasks() { + profilesTask.fork(); + eventsTask.fork(); + spansTask.fork(); + simConfigTask.fork(); + } + + /** + * Write the formatted SimulationResult JSON to System.out + * @param canceledListener The CanceledListener used during simulation. + * Used to determine if the results represent a canceled simulation. + */ + public void writeResults(CanceledListener canceledListener) { + final var stringWriter = new StringWriter(); + try(final var resultsJsonGenerator = Json.createGeneratorFactory(config).createGenerator(stringWriter)) { + forkSubTasks(); + + // Output the starting information + writeOpening(resultsJsonGenerator, canceledListener.get()); + print(resultsJsonGenerator, stringWriter); + + // Join the forked tasks + resultsJsonGenerator.write("simulationConfiguration", simConfigTask.join()); + print(resultsJsonGenerator, stringWriter); + + resultsJsonGenerator.write("profiles", profilesTask.join()); + print(resultsJsonGenerator, stringWriter); + + resultsJsonGenerator.write("spans", spansTask.join()); + print(resultsJsonGenerator, stringWriter); + + resultsJsonGenerator.write("events", eventsTask.join()); + resultsJsonGenerator.writeEnd(); + print(resultsJsonGenerator, stringWriter); + } + } + + /** + * Write the formatted SimulationResult JSON to the specified file. + * @param canceledListener The CanceledListener used during simulation. + * Used to determine if the results represent a canceled simulation. + * @param outputFilePath The file path to write results to. + */ + public void writeResults(CanceledListener canceledListener, Path outputFilePath) { + final var stringWriter = new StringWriter(); + try(final var resultsJsonGenerator = Json.createGeneratorFactory(config).createGenerator(stringWriter); + final var fileWriter = new FileWriter(outputFilePath.toFile())) + { + forkSubTasks(); + + // Output the starting information + writeOpening(resultsJsonGenerator, canceledListener.get()); + printFile(resultsJsonGenerator, stringWriter, fileWriter); + + // Join the forked tasks + resultsJsonGenerator.write("simulationConfiguration", simConfigTask.join()); + printFile(resultsJsonGenerator, stringWriter, fileWriter); + + resultsJsonGenerator.write("profiles", profilesTask.join()); + printFile(resultsJsonGenerator, stringWriter, fileWriter); + + resultsJsonGenerator.write("spans", spansTask.join()); + printFile(resultsJsonGenerator, stringWriter, fileWriter); + + resultsJsonGenerator.write("events", eventsTask.join()); + resultsJsonGenerator.writeEnd(); + printFile(resultsJsonGenerator, stringWriter, fileWriter); + + fileWriter.flush(); + System.out.println("Results written to "+outputFilePath); + } catch (IOException e) { + throw new RuntimeException("Unable to write to file: "+outputFilePath, e); + } + } + + /** Helper method that handles buffer management for printing to System.out */ + private void print(JsonGenerator resultsGenerator, StringWriter stringWriter) { + resultsGenerator.flush(); + System.out.print(stringWriter.toString().trim()); + // StringWriter.flush() does nothing, so we must clear the underlying buffer manually + stringWriter.getBuffer().setLength(0); + stringWriter.getBuffer().trimToSize(); // deallocates used buffer memory + } + + /** Helper method that handles buffer management for writing to a file */ + private void printFile(JsonGenerator resultsGenerator, StringWriter stringWriter, FileWriter fileWriter) + throws IOException { + resultsGenerator.flush(); + fileWriter.write(stringWriter.toString().trim()); + // StringWriter.flush() does nothing, so we must clear the underlying buffer manually + stringWriter.getBuffer().setLength(0); + stringWriter.getBuffer().trimToSize(); // deallocates used buffer memory + } + + /** Write the beginning and top-level fields of the results JSON */ + private void writeOpening(JsonGenerator resultsGenerator, boolean canceled) { + final var simEndTime = plan.simulationStartTimestamp.plusMicros(extent.in(Duration.MICROSECOND)); + + resultsGenerator.writeStartObject(); + resultsGenerator.write("version", SCHEMA_VERSION); + resultsGenerator.write("simulationStartTime", plan.simulationStartTimestamp.toString()); + resultsGenerator.write("simulationEndTime", simEndTime.toString()); + + if (canceled) { resultsGenerator.write("canceled", JsonValue.TRUE); } + else { resultsGenerator.write("canceled", JsonValue.FALSE); } + } + + /** Build up a JSON Object containing the resource profiles. */ + private JsonObject buildProfiles( + final Map> realProfiles, + final Map> discreteProfiles + ) { + final var realProfileBuilder = Json.createArrayBuilder(); + final var discreteProfileBuilder = Json.createArrayBuilder(); + + for (final var e : realProfiles.entrySet()) { + final var name = e.getKey(); + final var profile = e.getValue(); + + // Precompute segments + final var segmentsBuilder = Json.createArrayBuilder(); + profile.segments().forEach(s -> segmentsBuilder.add(Json.createObjectBuilder() + .add("extent", s.extent().toString()) + .add("dynamics", realDynamicsP.unparse(s.dynamics())))); + + final var profileBuilder = Json.createObjectBuilder() + .add("name", name) + .add("schema", valueSchemaP.unparse(profile.schema())) + .add("segments", segmentsBuilder); + + // Append to the array builder + realProfileBuilder.add(profileBuilder); + } + + for (final var e : discreteProfiles.entrySet()) { + final var name = e.getKey(); + final var profile = e.getValue(); + + // Precompute segments + final var segmentsBuilder = Json.createArrayBuilder(); + profile.segments().forEach(s -> segmentsBuilder.add(Json.createObjectBuilder() + .add("extent", s.extent().toString()) + .add("dynamics", serializedValueP.unparse(s.dynamics())))); + + final var profileBuilder = Json.createObjectBuilder() + .add("name", name) + .add("schema", valueSchemaP.unparse(profile.schema())) + .add("segments", segmentsBuilder); + + // Append to the array builder + discreteProfileBuilder.add(profileBuilder); + } + + return Json.createObjectBuilder() + .add("realProfiles", realProfileBuilder) + .add("discreteProfiles", discreteProfileBuilder) + .build(); + } + + /** + * Build up a JSON Object containing the resource profiles. + * Prioritizes getting profile segments from ResourceFileStreamer, + * using the Maps as fallbacks should a resource file be missing. + */ + private JsonObject buildProfiles( + final Map> realProfiles, + final Map> discreteProfiles, + final ResourceFileStreamer rfs + ) throws IOException + { + final var realProfileBuilder = Json.createArrayBuilder(); + final var discreteProfileBuilder = Json.createArrayBuilder(); + + for(final var e : realProfiles.entrySet()) { + final var name = e.getKey(); + final var profile = e.getValue(); + final var filepath = Path.of(rfs.getFileName(name)); + + // Precompute segments + final var segmentsBuilder = Json.createArrayBuilder(); + + try (final var stream = Files.lines(filepath)) { + stream.forEach(s -> { + if (!s.isBlank()) { + try (final JsonReader jr = Json.createReader(new StringReader(s))) { + segmentsBuilder.add(jr.readObject()); + } + } + }); + } + + // If somehow the file didn't exist and didn't except above, use the resources in the rmgr + if(!Files.deleteIfExists(filepath)){ + profile.segments().forEach(s -> segmentsBuilder.add(Json.createObjectBuilder() + .add("extent", s.extent().toString()) + .add("dynamics", realDynamicsP.unparse(s.dynamics())))); + } + + final var profileBuilder = Json.createObjectBuilder() + .add("name", name) + .add("schema", valueSchemaP.unparse(profile.schema())) + .add("segments", segmentsBuilder); + + // Append to the array builder + realProfileBuilder.add(profileBuilder); + } + + for(final var e : discreteProfiles.entrySet()) { + final var name = e.getKey(); + final var profile = e.getValue(); + final var filepath = Path.of(rfs.getFileName(name)); + + // Precompute segments + final var segmentsBuilder = Json.createArrayBuilder(); + try (final var stream = Files.lines(filepath)) { + stream.forEach(s -> { + if (!s.isBlank()) { + try (final JsonReader jr = Json.createReader(new StringReader(s))) { + segmentsBuilder.add(jr.readObject()); + } + } + }); + } + + // If somehow the file didn't exist and didn't except above, use the resources in the rmgr + if(!Files.deleteIfExists(filepath)){ + profile.segments().forEach(s -> segmentsBuilder.add(Json.createObjectBuilder() + .add("extent", s.extent().toString()) + .add("dynamics", serializedValueP.unparse(s.dynamics())))); + } + + final var profileBuilder = Json.createObjectBuilder() + .add("name",name) + .add("schema", valueSchemaP.unparse(profile.schema())) + .add("segments", segmentsBuilder); + + // Append to the array builder + discreteProfileBuilder.add(profileBuilder); + } + + return Json.createObjectBuilder() + .add("realProfiles", realProfileBuilder) + .add("discreteProfiles", discreteProfileBuilder) + .build(); + } + + /** Build up a JSON Object containing the activity spans. */ + private JsonObject buildSpans( + final Map simulatedActivities, + final Map unfinishedActivities, + final Timestamp simStartTime + ) { + final var simulatedActivitiesBuilder = Json.createArrayBuilder(); + final var unfinishedActivitiesBuilder = Json.createArrayBuilder(); + + for(final var e : simulatedActivities.entrySet()) { + final var id = e.getKey(); + final var act = e.getValue(); + + // Precompute complicated fields + final var childIdsBuilder = Json.createArrayBuilder(); + act.childIds().forEach(ci -> childIdsBuilder.add(ci.id())); + + final var startOffset = Duration.of(simStartTime.microsUntil(new Timestamp(act.start())), Duration.MICROSECOND).toString(); + final var endTime = act.start().plus(act.duration().in(Duration.MICROSECOND), ChronoUnit.MICROS).toString(); + + // Build activity's builder + final var actBuilder = Json.createObjectBuilder().add("id", id.id()); + + act.directiveId().ifPresentOrElse(did -> actBuilder.add("directiveId", did.id()), + () -> actBuilder.add("directiveId", JsonValue.NULL)); + if(act.parentId() != null) { actBuilder.add("parentId", act.parentId().id()); } + else { actBuilder.add("parentId", JsonValue.NULL); } + + actBuilder.add("childIds", childIdsBuilder) + .add("type", act.type()) + .add("startOffset", startOffset) + .add("duration", act.duration().toString()) + .add("attributes", serializedValueP.unparse(act.computedAttributes())) + .add("arguments", activityArgumentsP.unparse(act.arguments())) + .add("startTime", act.start().toString()) + .add("endTime", endTime); + + // Append to the array builder + simulatedActivitiesBuilder.add(actBuilder); + } + + for(final var e : unfinishedActivities.entrySet()) { + final var id = e.getKey(); + final var act = e.getValue(); + + // Precompute complicated fields + final var childIdsBuilder = Json.createArrayBuilder(); + act.childIds().forEach(ci -> childIdsBuilder.add(ci.id())); + + final var startOffset = Duration.of(simStartTime.microsUntil(new Timestamp(act.start())), Duration.MICROSECOND).toString(); + + // Build activity's builder + final var actBuilder = Json.createObjectBuilder().add("id", id.id()); + + act.directiveId().ifPresentOrElse(did -> actBuilder.add("directiveId", did.id()), + () -> actBuilder.add("directiveId", JsonValue.NULL)); + if(act.parentId() != null) { actBuilder.add("parentId", act.parentId().id()); } + else { actBuilder.add("parentId", JsonValue.NULL); } + + actBuilder.add("childIds", childIdsBuilder) + .add("type", act.type()) + .add("startOffset", startOffset) + .add("arguments", activityArgumentsP.unparse(act.arguments())) + .add("startTime", act.start().toString()); + + // Append to the array builder + unfinishedActivitiesBuilder.add(actBuilder); + } + + return Json.createObjectBuilder() + .add("simulatedActivities", simulatedActivitiesBuilder) + .add("unfinishedActivities", unfinishedActivitiesBuilder) + .build(); + } + + /** Build up a JSON Object containing the simulation events. */ + private JsonObject buildEvents(final Map>> events, final List> topics ) { + final var eventArrayBuilder = Json.createArrayBuilder(); + + for (final var eventPoint : events.entrySet()) { + final var realTime = eventPoint.getKey(); + final var transactions = eventPoint.getValue(); + + for (int transactionIndex = 0; transactionIndex < transactions.size(); transactionIndex++) { + final var eventGraph = transactions.get(transactionIndex); + final var flattenedEventGraph = EventGraphFlattener.flatten(eventGraph); + + for (final Pair entry : flattenedEventGraph) { + final EventRecord event = entry.getRight(); + final var eventBuilder = Json.createObjectBuilder() + .add("causalTime",entry.getLeft()) + .add("realTime",realTime.toString()) + .add("transactionIndex",transactionIndex) + .add("value", serializedValueP.unparse(event.value())); + + //grab the topic from the event's topic id + topics.stream() + .filter(topic -> topic.getLeft() == event.topicId()) + .findFirst() + .ifPresent(topic -> eventBuilder.add("topic", Json.createObjectBuilder() + .add("name",topic.getMiddle()) + .add("valueSchema", valueSchemaP.unparse(topic.getRight())))); + + // optional span id + event.spanId().ifPresentOrElse(spanId -> eventBuilder.add("spanId", spanId), + () -> eventBuilder.add("spanId", JsonValue.NULL)); + eventArrayBuilder.add(eventBuilder); + } + } + } + + return Json.createObjectBuilder() + .add("event",eventArrayBuilder.build()) + .build(); + } + + /** Build up a JSON Object containing the simulation configuration. */ + private JsonObject buildSimConfig(final Plan plan) { + return Json.createObjectBuilder() + .add("startTime", plan.simulationStartTimestamp.toString()) + .add("endTime",plan.simulationEndTimestamp.toString()) + .add("arguments", simulationArgumentsP.unparse(plan.simulationConfiguration())) + .build(); + } + +} + +/* +Json Schema for Sim results: + +{ +version: 1.0 +simulationStartTime: Timestamp (2024-07-01T00:00:00Z) +simulationEndTime: Timestamp // When the simulation stopped +canceled: boolean + +simulationConfiguration: { + startTime: Timestamp + endTime: Timestamp + arguments: {} +} + +profiles: { + realProfiles: [ + { + name: string + schema: ValueSchema + segments: [ + extent: Duration + dynamics: {}//arbitrary value based on schema + ] + } + ], + discreteProfiles: [ + { + name: string + schema: ValueSchema + segments: [ + extent: Duration + dynamics: {}//arbitrary value based on schema + ] + } + ] +} + +spans: { + simulatedActivities: [ + { + id: int + directiveId: int | null + parentId: int | null + childIds: [int] + type: String + startOffset: Duration + duration: Duration + attributes: {} + arguments: {} + startTime: Timestamp + endTime: Timestamp + } + ], + unfinishedActivities: [ + { + id: int + directiveId: int | null + parentId: int | null + childIds: [int] + type: string + startOffset: Duration + arguments: {} + startTime: Timestamp + } + ] +} + +events: { + causalTime : string, + realTime : Timestamp, + transactionIndex : int, + value : {}, + topic: { + name : string + valueSchema : {} + } + spanId: int, +} +} + */ diff --git a/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationUtility.java b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationUtility.java new file mode 100644 index 0000000000..153fca61e6 --- /dev/null +++ b/orchestration-utils/src/main/java/gov/nasa/jpl/aerie/orchestration/simulation/SimulationUtility.java @@ -0,0 +1,177 @@ +package gov.nasa.jpl.aerie.orchestration.simulation; + +import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; +import gov.nasa.jpl.aerie.merlin.driver.MissionModel; +import gov.nasa.jpl.aerie.merlin.driver.MissionModelBuilder; +import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; +import gov.nasa.jpl.aerie.merlin.driver.SimulationDriver; +import gov.nasa.jpl.aerie.merlin.driver.SimulationException; +import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; +import gov.nasa.jpl.aerie.merlin.driver.resources.InMemorySimulationResourceManager; +import gov.nasa.jpl.aerie.merlin.driver.resources.SimulationResourceManager; +import gov.nasa.jpl.aerie.merlin.driver.resources.StreamingSimulationResourceManager; +import gov.nasa.jpl.aerie.merlin.protocol.model.ModelType; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.Plan; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class SimulationUtility implements AutoCloseable { + private final ExecutorService exec; + private final SimulationResourceManager rmgr; + + /** + * Create a new SimulationUtility that manages resources using an InMemorySimulationResourceManager. + */ + public SimulationUtility() { + this.exec = Executors.newSingleThreadExecutor(); + rmgr = new InMemorySimulationResourceManager(); + } + + /** + * Create a new SimulationUtility that manages resources using a StreamingSimulationResourceManager. + * @param resourceStreamer a Consumer defining how the ResourceManager will stream resources. + */ + public SimulationUtility(ResourceFileStreamer resourceStreamer) { + this.exec = Executors.newSingleThreadExecutor(); + rmgr = new StreamingSimulationResourceManager(resourceStreamer); + } + + /** + * Load and instantiate a Mission Model from a JAR on the file system. + * + * @param modelJarPath Path to the JAR + * @param simulationStartTime The time the loaded model expects to be simulated starting at. + * Necessary to correctly instantiate internal resources. + * @param modelConfiguration The configuration to be used while instantiating the model. + * Expected contents defined by the Model's Configuration. + * @return An instantiated MissionModel + * @throws MissionModelLoader.MissionModelLoadException If there is an issue while loading the JAR, + * such as the JAR not existing at the specified path. + * @throws MissionModelLoader.MissionModelInstantiationException If there is an issue while instantiating the Model, + * such as a invalid configuration or simulationStartTime. + */ + public static MissionModel instantiateMissionModel( + Path modelJarPath, + Instant simulationStartTime, + Map modelConfiguration + ) throws MissionModelLoader.MissionModelLoadException, MissionModelLoader.MissionModelInstantiationException { + return MissionModelLoader.loadMissionModel( + simulationStartTime, + SerializedValue.of(modelConfiguration), + modelJarPath, + modelJarPath.getFileName().toString(), + "" + ); + } + + /** + * Instantiate a Mission Model using the generated Java code + * + * @param modelType An instance of the GeneratedModelType class created for the mission model by the merlin processor + * @param simulationStartTime The time the loaded model expects to be simulated starting at. + * Necessary to correctly instantiate internal resources. + * @param modelConfiguration The configuration to be used while instantiating the mission model. + * @return An instantiated MissionModel + * @param The mission model's Configuration class, as defined by the @WithConfiguration tag within its package-info.java + * @param The mission model's Model class, as defined by the @MissionModel tag within its package-info.java + */ + public static MissionModel instantiateMissionModel( + ModelType modelType, + Instant simulationStartTime, + Config modelConfiguration + ) { + final var modelBuilder = new MissionModelBuilder(); + final var registry = DirectiveTypeRegistry.extract(modelType); + + // TODO: [AERIE-1516] Teardown the model to release any system resources (e.g. threads). + final var model = modelType.instantiate(simulationStartTime, modelConfiguration, modelBuilder); + return modelBuilder.build(model, registry); + } + + /** + * Simulate a plan. Simulation will not be cancelable. + * @param model The mission model to be used during simulation + * @param plan The plan to simulate. Contains the simulation configuration + * @return A Future to get the SimulationResults + */ + public Future simulate(MissionModel model, Plan plan) { + return simulate(model, plan, () -> false, d -> {}); + } + + /** + * Simulate a plan + * @param model The mission model to be used during simulation + * @param plan The plan to simulate. Contains the simulation configuration + * @param canceledListener A boolean supplier to permit canceling of the simulation + * @param extentConsumer A duration consumer to receive updates on how much time has elapsed within the simulation + * @return A Future to get the SimulationResults + */ + public Future simulate( + MissionModel model, + Plan plan, + Supplier canceledListener, + Consumer extentConsumer + ) { + final var simulationDuration = Duration.of(plan.simulationStartTimestamp + .microsUntil(plan.simulationEndTimestamp), Duration.MICROSECOND); + final var resultsThread = new Callable() { + @Override + public SimulationResults call() { + return SimulationDriver.simulate( + model, + plan.activityDirectives(), + plan.simulationStartTimestamp.toInstant(), + simulationDuration, + plan.planStartInstant(), + plan.duration(), + canceledListener, + extentConsumer, + rmgr); + } + }; + + return exec.submit(resultsThread); + } + + /** + * Format a SimulationException into a JSON object. + */ + public static JsonObject formatSimulationException(SimulationException ex) { + final var dataBuilder = Json.createObjectBuilder() + .add("elapsedTime", SimulationException.formatDuration(ex.elapsedTime)) + .add("utcTimeDoy", SimulationException.formatInstant(ex.instant)); + ex.directiveId.ifPresent(directiveId -> dataBuilder.add("executingDirectiveId", directiveId.id())); + ex.activityType.ifPresent(activityType -> dataBuilder.add("executingActivityType", activityType)); + ex.activityStackTrace.ifPresent(activityStackTrace -> dataBuilder.add("activityStackTrace", activityStackTrace)); + + final var sw = new StringWriter(); + ex.cause.printStackTrace(new PrintWriter(sw, true)); + final var stackTrace = sw.toString(); + + return Json.createObjectBuilder() + .add("type", "SIMULATION_EXCEPTION") + .add("message", ex.cause.getMessage()) + .add("data", dataBuilder) + .add("trace", stackTrace) + .build(); + } + + @Override + public void close() { + exec.close(); + } +} diff --git a/scheduler-driver/build.gradle b/scheduler-driver/build.gradle index de8ee99f58..41c84dac59 100644 --- a/scheduler-driver/build.gradle +++ b/scheduler-driver/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'org.slf4j:slf4j-simple:2.0.7' implementation 'org.apache.commons:commons-collections4:4.4' implementation project(':merlin-framework') + implementation project(':type-utils') testImplementation project(':merlin-framework-junit') testImplementation project(':constraints') diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/DirectiveIdGenerator.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/DirectiveIdGenerator.java index 4b69e3f82a..cf178f9651 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/DirectiveIdGenerator.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/DirectiveIdGenerator.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; public class DirectiveIdGenerator { private long counter; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java index 823eb7f87b..b9de83ec9a 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java @@ -2,12 +2,12 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.time.Windows; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.goals.ActivityTemplateGoal; import gov.nasa.jpl.aerie.scheduler.goals.Goal; import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Optional; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingAssociationConflict.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingAssociationConflict.java index e1c7a6ef3c..a62f203551 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingAssociationConflict.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingAssociationConflict.java @@ -2,9 +2,9 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.time.Windows; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; import gov.nasa.jpl.aerie.scheduler.goals.Goal; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Collection; import java.util.Optional; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java index 6363bf4c9e..25a5852055 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java @@ -4,7 +4,6 @@ import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.constraints.time.Windows; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.Range; @@ -15,6 +14,7 @@ import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java index 18fc3f95f7..cf6c695231 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java @@ -7,7 +7,6 @@ import gov.nasa.jpl.aerie.constraints.time.Spans; import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.constraints.tree.Expression; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; @@ -19,6 +18,7 @@ import gov.nasa.jpl.aerie.scheduler.model.PersistentTimeAnchor; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; import java.util.ArrayList; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java index 5fc75c9e2a..59db44d9da 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java @@ -7,19 +7,15 @@ import gov.nasa.jpl.aerie.constraints.tree.And; import gov.nasa.jpl.aerie.constraints.tree.Expression; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; -import org.apache.commons.collections4.BidiMap; import java.util.LinkedList; import java.util.List; import java.util.Set; -import java.util.Optional; /** * describes some criteria that is desired in the solution plans diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java index be5d7c9912..3d9e2f23ec 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java @@ -2,18 +2,15 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.solver.optimizers.Optimizer; -import org.apache.commons.collections4.BidiMap; import org.apache.commons.lang3.NotImplementedException; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.Optional; public class OptionGoal extends Goal { diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Plan.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Plan.java index f4c2dec08f..b896bbdc40 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Plan.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Plan.java @@ -2,10 +2,10 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.solver.Evaluation; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Collection; import java.util.List; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/PlanInMemory.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/PlanInMemory.java index cc97ba5d0b..51b2ba5292 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/PlanInMemory.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/PlanInMemory.java @@ -2,10 +2,10 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.solver.Evaluation; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.ArrayList; import java.util.Collection; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java index c046c6a295..1af4804706 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java @@ -2,7 +2,6 @@ import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.model.LinearProfile; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; @@ -11,7 +10,6 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationData; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationResultsConverter; -import org.apache.commons.collections4.BidiMap; import java.util.ArrayList; import java.util.Collection; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulePlanGrounder.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulePlanGrounder.java index 8da3da77ca..f2d87a2ad0 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulePlanGrounder.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulePlanGrounder.java @@ -2,10 +2,10 @@ import gov.nasa.jpl.aerie.constraints.model.ActivityInstance; import gov.nasa.jpl.aerie.constraints.time.Interval; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.StartOffsetReducer; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.apache.commons.lang3.tuple.Pair; import java.util.HashMap; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulingActivity.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulingActivity.java index b2e55bc2f9..114df90155 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulingActivity.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/SchedulingActivity.java @@ -5,10 +5,10 @@ import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.constraints.tree.ProfileExpression; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Comparator; import java.util.HashMap; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacade.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacade.java index a597106377..9a921a510b 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacade.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacade.java @@ -1,11 +1,8 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.CachedSimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.CheckpointSimulationDriver; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.driver.SimulationResultsComputerInputs; import gov.nasa.jpl.aerie.merlin.framework.ThreadedTask; @@ -16,6 +13,9 @@ import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java index 14c82a3671..051a4908de 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java @@ -1,13 +1,13 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResultsComputerInputs; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.SchedulingInterruptedException; import gov.nasa.jpl.aerie.scheduler.model.ActivityType; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Collection; import java.util.HashSet; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacadeUtils.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacadeUtils.java index 69d510fd6a..772172fae5 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacadeUtils.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacadeUtils.java @@ -1,8 +1,5 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResultsComputerInputs; @@ -14,6 +11,9 @@ import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java index d5040493d0..de6564cb4a 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java @@ -6,7 +6,6 @@ import gov.nasa.jpl.aerie.constraints.time.Segment; import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.constraints.tree.Expression; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType; import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; @@ -35,6 +34,7 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationData; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.stn.TaskNetworkAdapter; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/FixedDurationTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/FixedDurationTest.java index 828b7ff81e..e3d5f57893 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/FixedDurationTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/FixedDurationTest.java @@ -5,7 +5,6 @@ import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeExpressionRelative; @@ -16,6 +15,7 @@ import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.scheduler.simulation.CheckpointSimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/ParametricDurationTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/ParametricDurationTest.java index bcc1632f89..158a819e44 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/ParametricDurationTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/ParametricDurationTest.java @@ -4,7 +4,6 @@ import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.scheduler.simulation.InMemoryCachedEngineStore; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; @@ -17,6 +16,7 @@ import gov.nasa.jpl.aerie.scheduler.model.Problem; import gov.nasa.jpl.aerie.scheduler.simulation.CheckpointSimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java index c5c6f82b7b..6272b58c81 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java @@ -5,7 +5,6 @@ import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeAnchor; @@ -24,6 +23,7 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.Evaluation; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.Test; import java.time.Instant; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SchedulingGrounderTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SchedulingGrounderTest.java index d42f887caf..6d5e425ab4 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SchedulingGrounderTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SchedulingGrounderTest.java @@ -2,12 +2,12 @@ import gov.nasa.jpl.aerie.constraints.model.ActivityInstance; import gov.nasa.jpl.aerie.constraints.time.Interval; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.model.ActivityType; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.SchedulePlanGrounder; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SimulationUtility.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SimulationUtility.java index b2e03d19c6..20ed13e4d5 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SimulationUtility.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/SimulationUtility.java @@ -5,13 +5,13 @@ import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.MissionModelBuilder; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.Problem; import gov.nasa.jpl.aerie.scheduler.simulation.InMemoryCachedEngineStore; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.scheduler.simulation.CheckpointSimulationFacade; +import gov.nasa.jpl.aerie.types.MissionModelId; import java.nio.file.Path; import java.time.Instant; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java index 80e391c7c6..a3e0f4721f 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java @@ -23,7 +23,6 @@ import gov.nasa.jpl.aerie.constraints.tree.SpansWrapperExpression; import gov.nasa.jpl.aerie.constraints.tree.ValueAt; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; @@ -40,6 +39,7 @@ import gov.nasa.jpl.aerie.scheduler.simulation.InMemoryCachedEngineStore; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestPersistentAnchor.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestPersistentAnchor.java index c4ac77b15f..2e022cfa57 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestPersistentAnchor.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestPersistentAnchor.java @@ -10,8 +10,6 @@ import gov.nasa.jpl.aerie.constraints.tree.Expression; import gov.nasa.jpl.aerie.constraints.tree.ForEachActivitySpans; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; @@ -23,6 +21,8 @@ import gov.nasa.jpl.aerie.scheduler.simulation.CheckpointSimulationFacade; import gov.nasa.jpl.aerie.scheduler.simulation.InMemoryCachedEngineStore; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.function.TriFunction; import org.junit.jupiter.api.Test; import org.slf4j.Logger; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/AnchorSchedulerTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/AnchorSchedulerTest.java index e9196c5a76..b09ef69b90 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/AnchorSchedulerTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/AnchorSchedulerTest.java @@ -1,14 +1,10 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.CachedSimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.CheckpointSimulationDriver; import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.OneStepTask; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; @@ -29,7 +25,10 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.TaskStatus; import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; -import gov.nasa.jpl.aerie.scheduler.SchedulingInterruptedException; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.SerializedActivity; import org.apache.commons.lang3.tuple.Triple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -138,7 +137,7 @@ private static void assertEqualsAsideFromChildren(ActivityInstance expected, Act @Test @DisplayName("Activities depending on no activities simulate at the correct time") - public void activitiesAnchoredToPlan() throws SchedulingInterruptedException { + public void activitiesAnchoredToPlan() { final var minusOneMinute = Duration.of(-60, Duration.SECONDS); final var resolveToPlanStartAnchors = new HashMap(415); final Map simulatedActivities = new HashMap<>(415); @@ -245,7 +244,7 @@ public void activitiesAnchoredToPlan() throws SchedulingInterruptedException { @Test @DisplayName("Activities depending on another activities simulate at the correct time") - public void activitiesAnchoredToOtherActivities() throws SchedulingInterruptedException { + public void activitiesAnchoredToOtherActivities() { final var allEndTimeAnchors = new HashMap(400); final var endTimeAnchorEveryFifth = new HashMap(400); final Map simulatedActivities = new HashMap<>(800); @@ -356,7 +355,7 @@ public void activitiesAnchoredToOtherActivities() throws SchedulingInterruptedEx @Test @DisplayName("Reference to anchored activities are correctly maintained by the driver") - public void activitiesAnchoredToOtherActivitiesSimple() throws SchedulingInterruptedException { + public void activitiesAnchoredToOtherActivitiesSimple() { final var activitiesToSimulate = new HashMap(2); activitiesToSimulate.put( new ActivityDirectiveId(0), @@ -371,7 +370,7 @@ public void activitiesAnchoredToOtherActivitiesSimple() throws SchedulingInterru @Test @DisplayName("Decomposition and anchors do not interfere with each other") - public void decomposingActivitiesAndAnchors() throws SchedulingInterruptedException{ + public void decomposingActivitiesAndAnchors() { // Given positions Left, Center, Right in an anchor chain, where each position can either contain a Non-Decomposition (ND) activity or a Decomposition (D) activity, // and the connection between Center and Left and Right and Center can be either Start (<-s-) or End (<-e-), // and two NDs cannot be adjacent to each other, there are 20 permutations. @@ -611,7 +610,7 @@ public void decomposingActivitiesAndAnchors() throws SchedulingInterruptedExcept @Test @DisplayName("Activities arranged in a wide anchor tree simulate at the correct time") - public void naryTreeAnchorChain() throws SchedulingInterruptedException{ + public void naryTreeAnchorChain() { // Full and complete 5-ary tree, 6 levels deep // Number of activity directives = 5^0 + 5^1 + 5^2 + 5^3 + 5^4 + 5^5 = 3906 diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacadeTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacadeTest.java index 6425adbd4f..6bf447e4b0 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacadeTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/CheckpointSimulationFacadeTest.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.framework.ThreadedTask; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -12,6 +11,7 @@ import gov.nasa.jpl.aerie.scheduler.model.PlanInMemory; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/InMemoryCachedEngineStoreTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/InMemoryCachedEngineStoreTest.java index 9edf6e229a..7334871761 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/InMemoryCachedEngineStoreTest.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/simulation/InMemoryCachedEngineStoreTest.java @@ -1,14 +1,14 @@ package gov.nasa.jpl.aerie.scheduler.simulation; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.CachedSimulationEngine; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.driver.engine.SimulationEngine; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.resources.InMemorySimulationResourceManager; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.SimulationUtility; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/scheduler-server/build.gradle b/scheduler-server/build.gradle index 80affd74df..c4ddee4905 100644 --- a/scheduler-server/build.gradle +++ b/scheduler-server/build.gradle @@ -24,6 +24,7 @@ dependencies { implementation project(':permissions') implementation project(':constraints') implementation project(':scheduler-driver') + implementation project(':type-utils') implementation 'io.javalin:javalin:5.6.3' implementation 'org.eclipse:yasson:3.0.3' diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchActivityInstanceException.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchActivityInstanceException.java index 37733a8f6f..9f1197099e 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchActivityInstanceException.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchActivityInstanceException.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.exceptions; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; public class NoSuchActivityInstanceException extends Exception { private final ActivityDirectiveId id; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchMissionModelException.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchMissionModelException.java index 050ff5ebb4..5dd1742cf6 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchMissionModelException.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchMissionModelException.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.exceptions; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; +import gov.nasa.jpl.aerie.types.MissionModelId; public class NoSuchMissionModelException extends Exception { private final MissionModelId id; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsers.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsers.java index 03e79be087..884ee472e8 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsers.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsers.java @@ -7,7 +7,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.scheduler.server.models.ActivityAttributesRecord; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.tuple.Pair; import org.postgresql.util.PGInterval; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/SchedulerParsers.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/SchedulerParsers.java index 21d2b1a770..abf6b5da35 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/SchedulerParsers.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/SchedulerParsers.java @@ -2,11 +2,11 @@ import gov.nasa.jpl.aerie.json.JsonParser; import gov.nasa.jpl.aerie.scheduler.server.models.HasuraAction; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleFailure; +import gov.nasa.jpl.aerie.types.MissionModelId; +import gov.nasa.jpl.aerie.types.Timestamp; import java.util.Optional; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/HasuraAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/HasuraAction.java index 8c111b7e64..c3c0c9b88a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/HasuraAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/HasuraAction.java @@ -1,5 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.models; +import gov.nasa.jpl.aerie.types.MissionModelId; + import java.util.Optional; public record HasuraAction(String name, I input, Session session) diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MerlinPlan.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MerlinPlan.java index 015f4550f3..64f24ea2b9 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MerlinPlan.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MerlinPlan.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.models; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.Collections; import java.util.HashMap; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MissionModelId.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MissionModelId.java deleted file mode 100644 index 917dc5f6d0..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/MissionModelId.java +++ /dev/null @@ -1,3 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.models; - -public record MissionModelId(long id) {} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java index 8f92a55986..42d471e34f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Specification.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.models; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.types.Timestamp; import java.util.List; import java.util.Map; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Timestamp.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Timestamp.java deleted file mode 100644 index c8459e74c0..0000000000 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/Timestamp.java +++ /dev/null @@ -1,50 +0,0 @@ -package gov.nasa.jpl.aerie.scheduler.server.models; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; - -public record Timestamp(ZonedDateTime time) { - // This builder must be used to get optional subsecond values - // See: https://stackoverflow.com/questions/30090710/java-8-datetimeformatter-parsing-for-optional-fractional-seconds-of-varying-sign - public static final DateTimeFormatter format = - new DateTimeFormatterBuilder() - .appendPattern("uuuu-DDD'T'HH:mm:ss") - .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true) - .toFormatter(); - - private Timestamp(String timestamp) throws DateTimeParseException { - this(LocalDateTime.parse(timestamp, format).atZone(ZoneOffset.UTC)); - } - - public Timestamp(Instant instant) { - this(instant.atZone(ZoneOffset.UTC)); - } - - public static Timestamp fromString(String timestamp) throws DateTimeParseException { - return new Timestamp(timestamp); - } - - public Timestamp plusMicros(final long micros) { - return new Timestamp(this.time.plus(micros, ChronoUnit.MICROS)); - } - - public long microsUntil(final Timestamp other) { - return this.time.until(other.time, ChronoUnit.MICROS); - } - - public Instant toInstant() { - return time.toInstant(); - } - - @Override - public String toString() { - return format.format(time); - } -} diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java index 7bbd88b621..0caeafdd23 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java index 19240ee2af..4254634d26 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationAction.java index 5ed47ae8d1..0a1639db26 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.intellij.lang.annotations.Language; import javax.json.Json; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java index bd4050a00d..6059c06b7a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java @@ -27,8 +27,8 @@ import gov.nasa.jpl.aerie.scheduler.model.PersistentTimeAnchor; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingDSL; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; import gov.nasa.jpl.aerie.scheduler.server.services.UnexpectedSubtypeError; +import gov.nasa.jpl.aerie.types.Timestamp; import org.apache.commons.lang3.function.TriFunction; import org.jetbrains.annotations.NotNull; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java index 8a17bc5279..65770d1da5 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java index 1282c5891d..0b6b9a5f79 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresParsers.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresParsers.java index 901a1502f3..07beef7596 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresParsers.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresParsers.java @@ -4,8 +4,8 @@ import gov.nasa.jpl.aerie.json.JsonParser; import gov.nasa.jpl.aerie.json.SchemaCache; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; import gov.nasa.jpl.aerie.scheduler.server.services.UnexpectedSubtypeError; +import gov.nasa.jpl.aerie.types.Timestamp; import javax.json.Json; import javax.json.JsonObject; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index 11455b06c2..87bd7daaf0 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -8,7 +8,6 @@ import java.util.HashMap; import java.util.Optional; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.SchedulingInterruptedException; import gov.nasa.jpl.aerie.scheduler.server.ResultsProtocol; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchRequestException; @@ -21,6 +20,7 @@ import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults.GoalResult; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java index c97bca9a34..c01fbe846a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/SpecificationRecord.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import java.util.Map; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GenerateSchedulingLibAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GenerateSchedulingLibAction.java index ff38298154..6782920a8b 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GenerateSchedulingLibAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GenerateSchedulingLibAction.java @@ -10,8 +10,8 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchMissionModelException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchPlanException; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; +import gov.nasa.jpl.aerie.types.MissionModelId; public record GenerateSchedulingLibAction( MerlinDatabaseService.ReaderRole merlinDatabaseService diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java index a2be8388c6..f1d3e5e111 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java @@ -4,8 +4,6 @@ import gov.nasa.jpl.aerie.constraints.model.LinearProfile; import gov.nasa.jpl.aerie.json.BasicParsers; import gov.nasa.jpl.aerie.json.JsonParser; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstance; import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; @@ -37,12 +35,14 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; import gov.nasa.jpl.aerie.scheduler.server.models.ProfileSet; import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; import gov.nasa.jpl.aerie.scheduler.server.models.UnwrappedProfileSet; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java index 8120ce5d95..8856c101fc 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.scheduler.server.services; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -17,10 +16,11 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import java.io.IOException; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java index ec7699dad3..755ca3c313 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java @@ -3,8 +3,8 @@ import java.util.Collection; import java.util.Map; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; /** * summary of results from running the scheduler, including goal satisfaction metrics and changes made diff --git a/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsersTest.java b/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsersTest.java index 7afe66fa4a..23ebddbcf2 100644 --- a/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsersTest.java +++ b/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/graphql/GraphQLParsersTest.java @@ -1,7 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.graphql; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; +import gov.nasa.jpl.aerie.types.Timestamp; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/scheduler-worker/build.gradle b/scheduler-worker/build.gradle index ccd1ccaebf..b139bcbc25 100644 --- a/scheduler-worker/build.gradle +++ b/scheduler-worker/build.gradle @@ -108,6 +108,7 @@ javadoc.options.addStringOption('Xdoclint:none', '-quiet') dependencies { implementation project(':merlin-driver') + implementation project(':type-utils') implementation project(':scheduler-driver') implementation project(':scheduler-server') implementation project(':parsing-utilities') diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java index dfc27ccaaa..56a1af35e2 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java @@ -20,9 +20,7 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; -import gov.nasa.jpl.aerie.merlin.driver.MissionModelId; import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; @@ -71,6 +69,8 @@ import gov.nasa.jpl.aerie.scheduler.simulation.InMemoryCachedEngineStore; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationData; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java index aead83f693..67ba1bbe02 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java @@ -1,7 +1,5 @@ package gov.nasa.jpl.aerie.scheduler.worker.services; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -16,11 +14,13 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinDatabaseService; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import java.nio.file.Path; diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java index dd6848d4b2..d2e0c2fc26 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java @@ -35,13 +35,13 @@ import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; -import gov.nasa.jpl.aerie.scheduler.server.models.MissionModelId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingDSL; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinDatabaseService; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinServiceException; +import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -80,45 +80,37 @@ public MerlinDatabaseService.MissionModelTypes getMissionModelTypes(final Missio } @Override - public long getPlanRevision(final PlanId planId) throws IOException, NoSuchPlanException, MerlinServiceException { + public long getPlanRevision(final PlanId planId) { return 0; } @Override - public PlanMetadata getPlanMetadata(final PlanId planId) - throws IOException, NoSuchPlanException, MerlinServiceException - { + public PlanMetadata getPlanMetadata(final PlanId planId) { return null; } @Override - public MerlinPlan getPlanActivityDirectives(final PlanMetadata planMetadata, final Problem mission) - throws IOException, NoSuchPlanException, MerlinServiceException, InvalidJsonException, InstantiationException - { + public MerlinPlan getPlanActivityDirectives(final PlanMetadata planMetadata, final Problem mission) { return null; } @Override - public void ensurePlanExists(final PlanId planId) throws IOException, NoSuchPlanException, MerlinServiceException { + public void ensurePlanExists(final PlanId planId) { } @Override - public Optional> getSimulationResults(final PlanMetadata planMetadata) - throws MerlinServiceException, IOException, InvalidJsonException - { + public Optional> getSimulationResults(final PlanMetadata planMetadata) { return Optional.empty(); } @Override - public ExternalProfiles getExternalProfiles(final PlanId planId) throws MerlinServiceException, IOException { + public ExternalProfiles getExternalProfiles(final PlanId planId) { return null; } @Override - public Collection getResourceTypes(final PlanId planId) - throws IOException, MerlinServiceException, NoSuchPlanException - { + public Collection getResourceTypes(final PlanId planId) { return null; } }; diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java index 70b2d2d71d..3242e7ef39 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingIntegrationTests.java @@ -27,10 +27,7 @@ import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.constraints.time.Segment; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective; -import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; -import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity; import gov.nasa.jpl.aerie.merlin.protocol.model.DirectiveType; import gov.nasa.jpl.aerie.merlin.protocol.model.InputType.Parameter; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -51,13 +48,16 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; -import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp; import gov.nasa.jpl.aerie.scheduler.server.remotes.postgres.SpecificationRevisionData; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinDatabaseService; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleRequest; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; +import gov.nasa.jpl.aerie.types.ActivityDirective; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; +import gov.nasa.jpl.aerie.types.SerializedActivity; +import gov.nasa.jpl.aerie.types.Timestamp; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -166,7 +166,7 @@ export default function myGoal() { specification : {duration: Temporal.Duration.from({seconds: 10})} }) } - """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); + """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); @@ -207,7 +207,7 @@ export default function myGoal() { specification : {occurrence: 10} }) } - """, true)),List.of(createAutoMutex("GrowBanana")),PLANNING_HORIZON); + """, true)),List.of(createAutoMutex("GrowBanana")),PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); @@ -655,7 +655,7 @@ export default () => Goal.CoexistenceGoal({ assertEquals(2, planByTime.get(MINUTES.times(10)).size()); var lookingFor = false; final var expectedCreation = new SerializedActivity("GrowBanana", - Map.of("quantity", SerializedValue.of(1), + Map.of("quantity", SerializedValue.of(1), "growingDuration", SerializedValue.of(MINUTES.in(MICROSECONDS)))); for(final var actAtTime10: planByTime.get(MINUTES.times(10))){ if(actAtTime10.serializedActivity().equals(expectedCreation)){ @@ -751,7 +751,7 @@ export default function myGoal() { specification : {duration: Temporal.Duration.from({seconds: 10})} }) } - """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); + """, true)),List.of(createAutoMutex("GrowBanana")), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); @@ -2531,7 +2531,7 @@ export default function myGoal() { interval: Temporal.Duration.from({hours: 1}) }) } - """, true)), + """, true)), PLANNING_HORIZON); assertEquals(96, results.updatedPlan().size()); } @@ -2553,7 +2553,7 @@ export default (): Goal => { interval: Temporal.Duration.from({ days: 30}) }) } - """, true)), PLANNING_HORIZON); + """, true)), PLANNING_HORIZON); //parent takes much more than 134 - 90 = 44 days to finish assertEquals(0, results.updatedPlan.size()); final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); @@ -3261,7 +3261,7 @@ export default () => Goal.CoexistenceGoal({ activityTemplate: ActivityTemplates.BananaNap(), startsAt: TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes: 5 })) }) - """, true, true) + """, true, true) ), PLANNING_HORIZON); @@ -3441,7 +3441,7 @@ export default function myGoal() { specification : {occurrence: 1} }) } - """, true)),List.of(), PLANNING_HORIZON); + """, true)),List.of(), PLANNING_HORIZON); assertEquals(1, results.scheduleResults.goalResults().size()); final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L, 0L)); diff --git a/settings.gradle b/settings.gradle index e8d8c7311b..cd72036376 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,6 +27,9 @@ include 'sequencing-server' include 'constraints' include 'scheduler-driver' +// Stateless Mode +include 'stateless-aerie' + // Testing include 'db-tests' include 'e2e-tests' @@ -38,3 +41,6 @@ include 'examples:config-with-defaults' include 'examples:config-without-defaults' include 'examples:minimal-mission-model' include 'examples:streamline-demo' +include 'stateless-aerie' +include 'orchestration-utils' +include 'type-utils' diff --git a/stateless-aerie/build.gradle b/stateless-aerie/build.gradle new file mode 100644 index 0000000000..fd6c9ac8bd --- /dev/null +++ b/stateless-aerie/build.gradle @@ -0,0 +1,91 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'jacoco' + id 'application' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + + withJavadocJar() + withSourcesJar() +} + +jar { + dependsOn(':parsing-utilities:jar') + dependsOn(':type-utils:jar') + dependsOn(':orchestration-utils:jar') + dependsOn(':constraints:jar') + dependsOn(':merlin-driver:jar') + dependsOn(':merlin-sdk:jar') + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + manifest { + attributes('Main-Class': 'gov.nasa.jpl.aerie.stateless.Main') + } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +test { + useJUnitPlatform { + includeEngines 'junit-jupiter' + } + testLogging { + exceptionFormat = 'full' + } + // Allow security manager so it can be overridden with mock for unit tests + systemProperty("java.security.manager", "allow") +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } +} + +// Link references to standard Java classes to the official Java 11 documentation. +javadoc.options.links 'https://docs.oracle.com/en/java/javase/11/docs/api/' +javadoc.options.links 'https://commons.apache.org/proper/commons-lang/javadocs/api-3.9/' +javadoc.options.addStringOption('Xdoclint:none', '-quiet') + +dependencies { + implementation project(':type-utils') + implementation project(':orchestration-utils') + implementation project(':merlin-driver') + implementation 'commons-cli:commons-cli:1.8.0' + + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' + testRuntimeOnly(project(':examples:foo-missionmodel')) + + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +publishing { + publications { + library(MavenPublication) { + version = findProperty('publishing.version') + from components.java + } + } + + publishing { + repositories { + maven { + name = findProperty("publishing.name") + url = findProperty("publishing.url") + credentials { + username = System.getenv(findProperty("publishing.usernameEnvironmentVariable")) + password = System.getenv(findProperty("publishing.passwordEnvironmentVariable")) + } + } + } + } +} diff --git a/stateless-aerie/src/main/java/gov/nasa/jpl/aerie/stateless/Main.java b/stateless-aerie/src/main/java/gov/nasa/jpl/aerie/stateless/Main.java new file mode 100644 index 0000000000..07c4acafd6 --- /dev/null +++ b/stateless-aerie/src/main/java/gov/nasa/jpl/aerie/stateless/Main.java @@ -0,0 +1,276 @@ +package gov.nasa.jpl.aerie.stateless; + +import gov.nasa.jpl.aerie.orchestration.simulation.CanceledListener; +import gov.nasa.jpl.aerie.orchestration.PlanJsonParser; +import gov.nasa.jpl.aerie.orchestration.simulation.ResourceFileStreamer; +import gov.nasa.jpl.aerie.orchestration.simulation.SimulationExtentConsumer; +import gov.nasa.jpl.aerie.orchestration.simulation.SimulationResultsWriter; +import gov.nasa.jpl.aerie.merlin.driver.MissionModel; +import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; +import gov.nasa.jpl.aerie.merlin.driver.SimulationException; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import gov.nasa.jpl.aerie.orchestration.simulation.SimulationUtility; +import gov.nasa.jpl.aerie.types.Plan; +import org.apache.commons.cli.*; + +import javax.json.Json; +import javax.json.stream.JsonGenerator; + +public class Main { + private static final String VERSION = "v2.16.0"; + private static final String FOOTER = "\nStateless Aerie "+VERSION; + + private static final Option HELP_OPTION = new Option("h", "help", false, "display this message and exit"); + + private sealed interface Arguments { + record SimulationArguments ( + MissionModel missionModel, + Plan plan, + boolean verbose, + Optional outputFilePath, + long extentUpdatePeriod + ) implements Arguments {} + } + + public static void main(String[] args) { + if(args.length == 0) { + displayTopLevelHelp(); + return; + } + + final var command = args[0]; + + switch (command.toLowerCase()) { + case "simulate": { + simulate(parseSimulationArgs(args)); + break; + } + case "-h": + case "--help": + default: + displayTopLevelHelp(); + break; + } + } + + private static Arguments.SimulationArguments parseSimulationArgs(String[] args) { + final Path modelJarPath; + final Path planJsonPath; + final Optional configJsonPath; + final boolean verbose; + final Optional outputFilePath; + final long extentUpdatePeriod; + + // Parse the command line arguments + final Options simulationOptions = createSimulationOptions(); + try { + checkForHelp(args, simulationOptions, "simulate", "Simulate a plan using the specified model and configuration"); + + final CommandLineParser parser = new DefaultParser(); + final CommandLine cmd = parser.parse(simulationOptions, args); + + modelJarPath = cmd.getParsedOptionValue('m'); + planJsonPath = cmd.getParsedOptionValue('p'); + verbose = cmd.hasOption("verbose"); + // Parser sets unused fields to 'null' + configJsonPath = cmd.getParsedOptionValue('s', Optional.empty()); + outputFilePath = cmd.getParsedOptionValue('f', Optional.empty()); + extentUpdatePeriod = cmd.getParsedOptionValue('i', 500L); + } catch (ParseException e) { + simulationOptions.addOption(HELP_OPTION); + new HelpFormatter().printHelp( + "stateless-aerie simulate", + "Simulate a plan using the specified model and configuration", + simulationOptions, + FOOTER, + true); + System.exit(2); + // The below is included as java doesn't recognize System.exit() as stopping the method, + // which causes compilation methods when trying to use the values assigned above + throw new RuntimeException(e); + } + + // Parse the plan and simulation config files into a Plan object + if (verbose) { System.out.println("Parsing plan "+planJsonPath+"..."); } + final var plan = PlanJsonParser.parsePlan(planJsonPath); + configJsonPath.ifPresent(path -> { + if (verbose) { System.out.println("Parsing simulation configuration "+path+"..."); } + PlanJsonParser.parseSimulationConfiguration(path, plan); + }); + + // Load the mission model + try { + if (verbose) { System.out.println("Loading mission model "+modelJarPath+"..."); } + final var model = SimulationUtility.instantiateMissionModel( + modelJarPath, + plan.simulationStartTimestamp.toInstant(), + plan.simulationConfiguration() + ); + + return new Arguments.SimulationArguments<>(model, plan, verbose, outputFilePath, extentUpdatePeriod); + } catch (MissionModelLoader.MissionModelLoadException | MissionModelLoader.MissionModelInstantiationException e) { + throw new RuntimeException("Error while loading mission model: "+modelJarPath, e); + } + } + + private static void simulate(Arguments.SimulationArguments simArgs) { + if (simArgs.verbose()) { System.out.println("Simulating Plan..."); } + + Thread shutdownHook = null; + final var rfs = new ResourceFileStreamer(); + final var canceledListener = new CanceledListener(); + + // Cancel support + try (final var extentConsumer = simArgs.verbose + ? new SimulationExtentConsumer(simArgs.extentUpdatePeriod) + : new SimulationExtentConsumer(); + final var simUtil = new SimulationUtility(rfs) + ) { + final var resultsFuture = simUtil.simulate( + simArgs.missionModel(), + simArgs.plan(), + canceledListener, + extentConsumer + ); + + shutdownHook = new Thread(() -> { + canceledListener.cancel(); + try { + final var results = resultsFuture.get(); + + if (simArgs.verbose()) { System.out.println("Writing Results..."); } + final var resultsWriter = new SimulationResultsWriter(results, simArgs.plan, rfs); + + simArgs.outputFilePath().ifPresentOrElse( + p -> resultsWriter.writeResults(canceledListener, p), + () -> resultsWriter.writeResults(canceledListener) + ); + + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + + // Surround awaiting sim results in a thread to output partial results during SIGINT + Runtime.getRuntime().addShutdownHook(shutdownHook); + final var results = resultsFuture.get(); + if (!canceledListener.get()) { + // Avoid two threads writing to the output file at the same time + Runtime.getRuntime().removeShutdownHook(shutdownHook); + + if (simArgs.verbose()) { System.out.println("Writing Results..."); } + final var resultsWriter = new SimulationResultsWriter(results, simArgs.plan, rfs); + simArgs.outputFilePath().ifPresentOrElse( + p -> resultsWriter.writeResults(canceledListener, p), + () -> resultsWriter.writeResults(canceledListener) + ); + } + } catch (ExecutionException e) { + if (e.getCause() instanceof SimulationException se) { + // Write Formatted Sim Exception to std.err + final Map config = Map.of(JsonGenerator.PRETTY_PRINTING, ""); + try(final var jsonWriter = Json.createWriterFactory(config).createWriter(System.err)) { + jsonWriter.writeObject(SimulationUtility.formatSimulationException(se)); + } + System.exit(1); + } + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (IllegalStateException ise) { + // If this is the message, it must've come from Runtime.getRuntime().removeShutdownHook and can be safely ignored + if (!ise.getMessage().contains("Shutdown in progress")) throw ise; + } finally { + // Try-catch wrapping in case this is executed while the shutdown hook is running. + try { Runtime.getRuntime().removeShutdownHook(shutdownHook); } + catch (IllegalStateException ise) {} + } + } + + /** + * Display top-level help for the application + */ + private static void displayTopLevelHelp() { + System.out.printf( + """ + usage: stateless-aerie COMMAND [ARGS]... + + Available commands: + - simulate: Simulate a plan using the specified model and configuration + %s + %n""", FOOTER); + } + + /** + * Build the parser options for the "simulate" command. + */ + private static Options createSimulationOptions() { + // Required Args + final Option modelPath = new Option("m", "model", true, "path to model jar"); + modelPath.setRequired(true); + modelPath.setConverter(Path::of); + + final Option planPath = new Option("p", "plan", true, "path to plan json"); + planPath.setRequired(true); + planPath.setConverter(Path::of); + + // Optional Path Args + final Option simConfigPath = new Option("s", "sim_config", true, "path to simulation configuration json"); + simConfigPath.setRequired(false); + simConfigPath.setConverter(s -> Optional.of(Path.of(s))); + + final Option outputFile = new Option("f", "file", true, "output file path"); + outputFile.setRequired(false); + outputFile.setConverter(f -> Optional.of(Path.of(f))); + + // Other Optional Args + final Option verbose = new Option("v", "verbose", false, "verbosity of simulation"); + + final Option extentUpdateFrequency = new Option("i", "update_interval", true, "minimum interval that simulation extent updates are posted, in milliseconds" ); + extentUpdateFrequency.setRequired(false); + extentUpdateFrequency.setConverter(Long::parseLong); + + final Options simulationOptions = new Options(); + simulationOptions.addOption(verbose); + simulationOptions.addOption(modelPath); + simulationOptions.addOption(planPath); + simulationOptions.addOption(simConfigPath); + simulationOptions.addOption(outputFile); + simulationOptions.addOption(extentUpdateFrequency); + return simulationOptions; + } + + /** + * Check if the "help" option was passed for a given command + * and, if so, print the command's help message and exit the program with status code 0. + * Checked independently to avoid required args for the command causing parsing issues. + * @param args the args passed into the commandline. + * @param subCommandOptions the parser options normally used to parse this command. + * @param subcommand the name of the subcommand (ie "simulate"). + * @param subcommandDescription the description of what the subcommand does. + */ + private static void checkForHelp( + String[] args, + Options subCommandOptions, + String subcommand, + String subcommandDescription + ) throws ParseException { + for(final var opt : args) { + if (opt.equals("-h") || opt.equals("--help")) { + subCommandOptions.addOption(HELP_OPTION); + new HelpFormatter().printHelp( + "stateless-aerie " + subcommand, + subcommandDescription, + subCommandOptions, + FOOTER, + true); + System.exit(0); + } + } + } +} diff --git a/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/CLIArgumentsTest.java b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/CLIArgumentsTest.java new file mode 100644 index 0000000000..b043aa7657 --- /dev/null +++ b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/CLIArgumentsTest.java @@ -0,0 +1,302 @@ +package gov.nasa.jpl.aerie.stateless; + +import gov.nasa.jpl.aerie.stateless.utils.BlockExitSecurityManager; +import gov.nasa.jpl.aerie.stateless.utils.SystemExit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import javax.json.Json; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringReader; +import java.util.List; + +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 static org.junit.jupiter.api.Assertions.assertTrue; + +public class CLIArgumentsTest { + private ByteArrayOutputStream out; + private ByteArrayOutputStream err; + private PrintStream outputStream; + private PrintStream errorStream; + + @BeforeEach + void beforeEach() { + // Redirect System streams to buffers that can be examined + out = new ByteArrayOutputStream(); + err = new ByteArrayOutputStream(); + outputStream = new PrintStream(out); + errorStream = new PrintStream(err); + + System.setOut(outputStream); + System.setErr(errorStream); + } + + @AfterEach + void afterEach() { + outputStream.close(); + errorStream.close(); + } + + /** + * Top level help text only displays if: + * - the first argument is '-h' or '--help' + * - no arguments are passed + * - a nonexistent subcommand is passed + */ + @Test + void topLevelHelp() { + final String helpString = + """ + usage: stateless-aerie COMMAND [ARGS]... + + Available commands: + - simulate: Simulate a plan using the specified model and configuration + + Stateless Aerie v"""; + + final var validArgs = new String[][]{{"-h"}, {"--help"}, {}, {"fakeCommand"}, + {"-h", "simulate"}, {"--help", "simulate"}, + {"-h", "--help"}, {"--help", "-h"}}; + for(final var args : validArgs) { + Main.main(args); + outputStream.flush(); + assertTrue(out.toString().contains(helpString)); + assertTrue(err.toString().isBlank()); + out.reset(); + err.reset(); + } + + // '-h' or '--help' as the second argument does not display the top-level help string + final var otherArgs = new String[][]{{"simulate", "-h"}, {"simulate", "--help"}}; + BlockExitSecurityManager.install(); + for(final var args : otherArgs) { + final var sysExit = assertThrows(SystemExit.class, () -> Main.main(args)); + assertEquals(0, sysExit.getStatusCode()); + + outputStream.flush(); + assertFalse(out.toString().contains(helpString)); + assertTrue(err.toString().isBlank()); + out.reset(); + err.reset(); + } + BlockExitSecurityManager.uninstall(); + } + + @Nested + public class SimulationArguments { + /** + * Subcommand help message appears if the '-h' or '--help' flag is passed after the subcommand, + * regardless of presence of other arguments or position. + */ + @Test + void simulationHelp() { + final var helpString = + """ + usage: stateless-aerie simulate [-f ] [-h] [-i ] -m -p + [-s ] [-v] + Simulate a plan using the specified model and configuration + -f,--file output file path + -h,--help display this message and exit + -i,--update_interval minimum interval that simulation extent + updates are posted, in milliseconds + -m,--model path to model jar + -p,--plan path to plan json + -s,--sim_config path to simulation configuration json + -v,--verbose verbosity of simulation + + Stateless Aerie v"""; + + final var helpArgs = new String[][] {{"simulate", "-h"}, {"simulate", "--help"}, + {"simulate", "-p", "foo plan.json", "-h"}, + {"simulate", "-i", "1000", "--help"}}; + + BlockExitSecurityManager.install(); + for (final var args : helpArgs) { + final var sysExit = assertThrows(SystemExit.class, () -> Main.main(args)); + assertEquals(0, sysExit.getStatusCode()); + + outputStream.flush(); + assertTrue(out.toString().contains(helpString)); + assertTrue(err.toString().isBlank()); + out.reset(); + err.reset(); + } + BlockExitSecurityManager.uninstall(); + } + + /** An exception is thrown if simulation is run for a plan that doesn't exist or cannot be parsed. */ + @Test + void badPlan() { + final var missingFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/fake_plan.json"})); + assertEquals("Specified plan JSON file does not exist: src/test/resources/fake_plan.json", + missingFileError.getMessage()); + + final var badFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar"})); + assertEquals("Error while reading plan JSON file: ../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + badFileError.getMessage()); + } + + /** An exception is thrown if simulation is run using a model that doesn't exist or cannot be parsed. */ + @Test + void badModel() { + final var missingFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "fake-mission-model.jar", + "-p", "src/test/resources/simpleFooPlan.json"})); + assertEquals("Error while loading mission model: fake-mission-model.jar", + missingFileError.getMessage()); + + final var badFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "src/test/resources/simpleFooPlan.json", + "-p", "src/test/resources/simpleFooPlan.json"})); + assertEquals("Error while loading mission model: src/test/resources/simpleFooPlan.json", + badFileError.getMessage()); + } + + /** An exception is thrown if simulation is run using a sim config that doesn't exist or cannot be parsed. */ + @Test + void badSimConfig() { + final var missingFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json", + "-s", "src/test/resources/fake_config.json"})); + assertEquals("Specified simulation configuration JSON file does not exist: src/test/resources/fake_config.json", + missingFileError.getMessage()); + + final var badFileError = assertThrows(RuntimeException.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json", + "-s", "src/test/resources/simpleFooPlan.json"})); + assertEquals("Error while reading simulation configuration JSON file: src/test/resources/simpleFooPlan.json", + badFileError.getMessage()); + } + + /** When verbose is on, progress is reported prior to simulation results. */ + @Test + void verboseOn() throws IOException { + Main.main(new String[]{"simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json", + // Extent interval cranked way up to guarantee no extent is printed (5000s) + "-i", "5000000000", + "--verbose"}); + outputStream.flush(); + try(final var reader = new BufferedReader(new FileReader("src/test/resources/simpleFooPlanResults.json"))) { + final var fileLines = reader.lines().toList(); + final var output = out.toString(); + assertEquals(fileLines.size() + 4, output.split("\n").length); + + int truncateIndex = 0; + for(int i = 0; i < 4; ++i) { + truncateIndex = output.indexOf("\n", truncateIndex + 1); + } + + final var introLines = """ + Parsing plan src/test/resources/simpleFooPlan.json... + Loading mission model ../examples/foo-missionmodel/build/libs/foo-missionmodel.jar... + Simulating Plan... + Writing Results..."""; + + assertEquals(introLines, output.substring(0, truncateIndex)); + + try(final var fileReader = Json.createReader(new FileReader("src/test/resources/simpleFooPlanResults.json")); + final var outputReader = Json.createReader(new StringReader(output.substring(truncateIndex)))) { + final var fileJson = fileReader.readObject(); + final var outputJson = outputReader.readObject(); + assertEquals(fileJson, outputJson); + } + } + } + + /** When verbose is off, only simulation results are output. */ + @Test + void verboseOff() throws IOException { + Main.main(new String[]{"simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json"}); + outputStream.flush(); + + try(final var fileReader = Json.createReader(new FileReader("src/test/resources/simpleFooPlanResults.json")); + final var outputReader = Json.createReader(new StringReader(out.toString()))) { + final var fileJson = fileReader.readObject(); + final var outputJson = outputReader.readObject(); + assertEquals(fileJson, outputJson); + } + } + + /** Sim config bounds take precedence over plan bounds */ + @Test + void simConfigTemporalSubset() throws FileNotFoundException { + Main.main(new String[] {"simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json", + "-s", "src/test/resources/temporalSubsetFooConfiguration.json"}); + try(final var fileReader = Json.createReader(new FileReader("src/test/resources/subsetFooPlanResults.json")); + final var outputReader = Json.createReader(new StringReader(out.toString()))) { + final var fileJson = fileReader.readObject(); + final var outputJson = outputReader.readObject(); + assertEquals(fileJson, outputJson); + } + } + + /** + * Sim exceptions are given as a well-formatted JSON. + * Also tests that sim config arguments are applied to simulation. + */ + @Test + void simException() { + BlockExitSecurityManager.install(); + final var sysExit = assertThrows(SystemExit.class, + () -> Main.main(new String[]{ + "simulate", + "-m", "../examples/foo-missionmodel/build/libs/foo-missionmodel.jar", + "-p", "src/test/resources/simpleFooPlan.json", + "-s", "src/test/resources/exceptionFooConfiguration.json",})); + assertEquals(1, sysExit.getStatusCode()); + BlockExitSecurityManager.uninstall(); + + assertTrue(out.toString().isBlank()); + assertFalse(err.toString().isBlank()); + + try(final var errorReader = Json.createReader(new StringReader(err.toString()))) { + final var errorJson = errorReader.readObject(); + final var dataObject = Json.createObjectBuilder() + .add("elapsedTime", "01:00:00.000000") + .add( "utcTimeDoy", "2024-183T01:00:00") + .build(); + + assertEquals(4, errorJson.keySet().size()); + assertTrue(errorJson.keySet().containsAll(List.of("type", "message", "data", "trace"))); + + assertEquals("SIMULATION_EXCEPTION", errorJson.getString("type")); + assertEquals("Daemon task exception raised.", errorJson.getString("message")); + assertEquals(dataObject, errorJson.getJsonObject("data")); + assertTrue(errorJson.getString("trace").startsWith("java.lang.RuntimeException: Daemon task exception raised.")); + } + } + } +} diff --git a/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/FileNameGeneratorTest.java b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/FileNameGeneratorTest.java new file mode 100644 index 0000000000..6dde81de66 --- /dev/null +++ b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/FileNameGeneratorTest.java @@ -0,0 +1,44 @@ +package gov.nasa.jpl.aerie.stateless; + +import gov.nasa.jpl.aerie.orchestration.simulation.ResourceFileStreamer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class FileNameGeneratorTest { + + @ParameterizedTest + @ValueSource(chars = {'<', '>', ':', '"', '\\', '/', '|', '?', '*', '.', ',', '+', '&', '\'', ' '}) + public void testFileNameGenerationWithForbiddenChars(char c) { + final var resourceName = c+"This"+c+"name"+c+"has"+c+"forbidden"+c+"chars"; + final var expectedFileName = "_This_name_has_forbidden_chars"; + + assertTrue(new ResourceFileStreamer().getFileName(resourceName).contains(expectedFileName)); + + } + + @Test + public void testFileNameGenerationRandomForbiddenChars() { + final char[] forbiddenChars = {'<', '>', ':', '"', '\\', '/', '|', '?', '*', '.', ',', '+', '&', '\'', ' '}; + final var fileGenerator = new ResourceFileStreamer(); + + final var random = new Random(); + for (int i = 0; i < 1000; i++) { + final var resourceNameBuilder = new StringBuilder(); + final var expectedFileNameBuilder = new StringBuilder(); + + for (int w = 0; w < 5; w++) { // 5 words + final var randomChar = forbiddenChars[random.nextInt(forbiddenChars.length)]; + resourceNameBuilder.append(randomChar).append("hello").append(randomChar); + expectedFileNameBuilder.append("_").append("hello").append("_"); + } + resourceNameBuilder.append("world"); + expectedFileNameBuilder.append("world"); + + assertTrue(fileGenerator.getFileName(resourceNameBuilder.toString()).contains(expectedFileNameBuilder.toString())); + } + } +} diff --git a/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/BlockExitSecurityManager.java b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/BlockExitSecurityManager.java new file mode 100644 index 0000000000..f311f30da2 --- /dev/null +++ b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/BlockExitSecurityManager.java @@ -0,0 +1,69 @@ +package gov.nasa.jpl.aerie.stateless.utils; + +import java.security.Permission; + +/** + * A SecurityManager that throws an exception when System.exit() is called instead of stopping the JVM. + */ +/* + SecurityManager is set to be deprecated by JEP 411 (https://openjdk.org/jeps/411) + However, our usage falls into the niche that currently has no followup: + - Evaluate whether new APIs or mechanisms are needed to address specific narrow use cases + for which the Security Manager has been employed, such as blocking System::exit. + Relevant open JDK ticket: https://bugs.openjdk.org/browse/JDK-8199704 + Once that ticket is closed, this class can be updated to use the new API +*/ +@SuppressWarnings("removal") +public class BlockExitSecurityManager extends SecurityManager { + private static SecurityManager originalSecurityManager; + private static boolean installed = false; + + private BlockExitSecurityManager(){} + + /** + * Set a BlockExitSecurityManager as the current Security Manager, if it is not already in use. + */ + public static void install() { + if(installed) { + throw new IllegalStateException("BlockExitSecurityManager is already in use."); + } + + installed = true; + originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new BlockExitSecurityManager()); + } + + /** + * If a BlockExitSecurityManager is the current Security Manager, + * restore the security manager it replaced as the current manager. + * + * Note that this method cannot detect if a new SecurityManager was installed between calling `install` and `uninstall`. + */ + public static void uninstall() { + if(!installed) { + throw new IllegalStateException("BlockExitSecurityManager is not in use."); + } + + installed = false; + System.setSecurityManager(originalSecurityManager); + } + + /** + * Block system exit and instead throw as an exception with the specified status code + */ + @Override + public void checkExit(final int statusCode) { + throw new SystemExit(statusCode); + } + + /** + * Defer permission checks to the original security manager, if it exists. + * Without this override, the JVM hangs during "BlockExitSecurityManager::uninstall" + */ + @Override + public void checkPermission(Permission perm) { + if(originalSecurityManager != null) { + originalSecurityManager.checkPermission(perm); + } + } +} diff --git a/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/SystemExit.java b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/SystemExit.java new file mode 100644 index 0000000000..65d582738e --- /dev/null +++ b/stateless-aerie/src/test/java/gov/nasa/jpl/aerie/stateless/utils/SystemExit.java @@ -0,0 +1,10 @@ +package gov.nasa.jpl.aerie.stateless.utils; + +public class SystemExit extends RuntimeException { + private final int statusCode; + + public SystemExit(int statusCode) { + this.statusCode = statusCode; + } + public int getStatusCode() { return statusCode; } +} diff --git a/stateless-aerie/src/test/resources/exceptionFooConfiguration.json b/stateless-aerie/src/test/resources/exceptionFooConfiguration.json new file mode 100644 index 0000000000..027612abaa --- /dev/null +++ b/stateless-aerie/src/test/resources/exceptionFooConfiguration.json @@ -0,0 +1,8 @@ +{ + "version": "2", + "simulation_start_time": "2024-07-01T00:00:00+00:00", + "simulation_end_time": "2024-07-02T00:00:00+00:00", + "arguments": { + "raiseException": true + } +} diff --git a/stateless-aerie/src/test/resources/simpleFooPlan.json b/stateless-aerie/src/test/resources/simpleFooPlan.json new file mode 100644 index 0000000000..400fa25cf8 --- /dev/null +++ b/stateless-aerie/src/test/resources/simpleFooPlan.json @@ -0,0 +1,40 @@ +{ + "activities": [ + { + "anchor_id": null, + "anchored_to_start": true, + "arguments": { + "duration": { + "amountInMicroseconds": 2000000 + } + }, + "id": 4, + "metadata": {}, + "name": "BasicFooActivity", + "start_offset": "02:27:15.059", + "tags": [], + "type": "BasicFooActivity" + }, + { + "anchor_id": null, + "anchored_to_start": true, + "arguments": { + "minutesElapsed": 700, + "spawnDelay": 1 + }, + "id": 5, + "metadata": {}, + "name": "DaemonCheckerSpawner", + "start_offset": "11:39:55.219", + "tags": [], + "type": "DaemonCheckerSpawner" + } + ], + "duration": "24:00:00", + "id": 3, + "model_id": 4, + "name": "foo plan", + "simulation_arguments": {}, + "start_time": "2024-07-01T00:00:00+00:00", + "tags": [] +} diff --git a/stateless-aerie/src/test/resources/simpleFooPlanResults.json b/stateless-aerie/src/test/resources/simpleFooPlanResults.json new file mode 100644 index 0000000000..07fd5cfd89 --- /dev/null +++ b/stateless-aerie/src/test/resources/simpleFooPlanResults.json @@ -0,0 +1,909 @@ +{ + "version": 1.0, + "simulationStartTime": "2024-183T00:00:00", + "simulationEndTime": "2024-184T00:00:00", + "canceled": false, + "simulationConfiguration": { + "startTime": "2024-183T00:00:00", + "endTime": "2024-184T00:00:00", + "arguments": { + } + }, + "profiles": { + "realProfiles": [ + { + "name": "/utcClock", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 1000.0 + } + } + ] + }, + { + "name": "/simple_data/b/volume", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 5.0 + } + } + ] + }, + { + "name": "/simple_data/a/volume", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 10.0 + } + } + ] + }, + { + "name": "/sink/rate", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.5, + "rate": 0.0 + } + } + ] + }, + { + "name": "/source/rate", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 1.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/sink", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 0.5 + } + } + ] + }, + { + "name": "/simple_data/a/rate", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 10.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/batterySoC", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 100.0, + "rate": 0.5 + } + } + ] + }, + { + "name": "/simple_data/b/rate", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 5.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/data/rate", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 42.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/data", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 42.0 + } + } + ] + }, + { + "name": "/simple_data/total_volume", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 15.0 + } + } + ] + }, + { + "name": "/source", + "schema": { + "type": "struct", + "items": { + "rate": { + "type": "real" + }, + "initial": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": { + "initial": 100.0, + "rate": 1.0 + } + } + ] + } + ], + "discreteProfiles": [ + { + "name": "/foo", + "schema": { + "type": "real" + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": 21.0 + } + ] + }, + { + "name": "/foo/conflicted", + "schema": { + "type": "boolean" + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": false + } + ] + }, + { + "name": "/counter", + "schema": { + "type": "int" + }, + "segments": [ + { + "extent": "+00:16:40.000000", + "dynamics": 0 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 1 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 2 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 3 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 4 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 5 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 6 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 7 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 8 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 9 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 10 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 11 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 12 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 13 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 14 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 15 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 16 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 17 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 18 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 19 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 20 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 21 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 22 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 23 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 24 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 25 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 26 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 27 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 28 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 29 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 30 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 31 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 32 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 33 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 34 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 35 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 36 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 37 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 38 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 39 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 40 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 41 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 42 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 43 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 44 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 45 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 46 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 47 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 48 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 49 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 50 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 51 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 52 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 53 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 54 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 55 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 56 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 57 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 58 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 59 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 60 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 61 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 62 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 63 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 64 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 65 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 66 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 67 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 68 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 69 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 70 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 71 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 72 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 73 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 74 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 75 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 76 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 77 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 78 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 79 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 80 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 81 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 82 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 83 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 84 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 85 + }, + { + "extent": "+00:06:40.000000", + "dynamics": 86 + } + ] + }, + { + "name": "/activitiesExecuted", + "schema": { + "type": "int" + }, + "segments": [ + { + "extent": "+02:27:17.059000", + "dynamics": 0 + }, + { + "extent": "+21:32:42.941000", + "dynamics": 1 + } + ] + }, + { + "name": "/foo/starting_after_unix_epoch", + "schema": { + "type": "boolean" + }, + "segments": [ + { + "extent": "+24:00:00.000000", + "dynamics": true + } + ] + } + ] + }, + "spans": { + "simulatedActivities": [ + { + "id": 1, + "directiveId": null, + "parentId": 5, + "childIds": [ + ], + "type": "DaemonCheckerActivity", + "startOffset": "+11:40:55.219000", + "duration": "+00:00:00.000000", + "attributes": { + }, + "arguments": { + "minutesElapsed": 700 + }, + "startTime": "2024-07-01T11:40:55.219Z", + "endTime": "2024-07-01T11:40:55.219Z" + }, + { + "id": 4, + "directiveId": 4, + "parentId": null, + "childIds": [ + ], + "type": "BasicFooActivity", + "startOffset": "+02:27:15.059000", + "duration": "+00:00:02.000000", + "attributes": { + }, + "arguments": { + "duration": { + "amountInMicroseconds": 2000000 + } + }, + "startTime": "2024-07-01T02:27:15.059Z", + "endTime": "2024-07-01T02:27:17.059Z" + }, + { + "id": 5, + "directiveId": 5, + "parentId": null, + "childIds": [ + 1 + ], + "type": "DaemonCheckerSpawner", + "startOffset": "+11:39:55.219000", + "duration": "+00:01:00.000000", + "attributes": { + }, + "arguments": { + "minutesElapsed": 700, + "spawnDelay": 1 + }, + "startTime": "2024-07-01T11:39:55.219Z", + "endTime": "2024-07-01T11:40:55.219Z" + } + ], + "unfinishedActivities": [ + ] + }, + "events": { + "event": [ + { + "causalTime": ".1", + "realTime": "+02:27:15.059000", + "transactionIndex": 0, + "value": { + "duration": { + "amountInMicroseconds": 2000000 + } + }, + "topic": { + "name": "ActivityType.Input.BasicFooActivity", + "valueSchema": { + "type": "struct", + "items": { + "duration": { + "type": "struct", + "items": { + "amountInMicroseconds": { + "type": "int" + } + } + } + } + } + }, + "spanId": 4 + }, + { + "causalTime": ".1", + "realTime": "+02:27:17.059000", + "transactionIndex": 0, + "value": { + }, + "topic": { + "name": "ActivityType.Output.BasicFooActivity", + "valueSchema": { + "type": "struct", + "items": { + } + } + }, + "spanId": 4 + }, + { + "causalTime": ".1", + "realTime": "+11:39:55.219000", + "transactionIndex": 0, + "value": { + "minutesElapsed": 700, + "spawnDelay": 1 + }, + "topic": { + "name": "ActivityType.Input.DaemonCheckerSpawner", + "valueSchema": { + "type": "struct", + "items": { + "minutesElapsed": { + "type": "int" + }, + "spawnDelay": { + "type": "int" + } + } + } + }, + "spanId": 5 + }, + { + "causalTime": ".1", + "realTime": "+11:40:55.219000", + "transactionIndex": 0, + "value": { + "minutesElapsed": 700 + }, + "topic": { + "name": "ActivityType.Input.DaemonCheckerActivity", + "valueSchema": { + "type": "struct", + "items": { + "minutesElapsed": { + "type": "int" + } + } + } + }, + "spanId": 1 + }, + { + "causalTime": ".2", + "realTime": "+11:40:55.219000", + "transactionIndex": 0, + "value": { + }, + "topic": { + "name": "ActivityType.Output.DaemonCheckerActivity", + "valueSchema": { + "type": "struct", + "items": { + } + } + }, + "spanId": 1 + }, + { + "causalTime": ".1", + "realTime": "+11:40:55.219000", + "transactionIndex": 1, + "value": { + }, + "topic": { + "name": "ActivityType.Output.DaemonCheckerSpawner", + "valueSchema": { + "type": "struct", + "items": { + } + } + }, + "spanId": 5 + } + ] + } +} \ No newline at end of file diff --git a/stateless-aerie/src/test/resources/subsetFooPlanResults.json b/stateless-aerie/src/test/resources/subsetFooPlanResults.json new file mode 100644 index 0000000000..98a8346d75 --- /dev/null +++ b/stateless-aerie/src/test/resources/subsetFooPlanResults.json @@ -0,0 +1,559 @@ +{ + "version": 1.0, + "simulationStartTime": "2024-183T12:00:00", + "simulationEndTime": "2024-184T00:00:00", + "canceled": false, + "simulationConfiguration": { + "startTime": "2024-183T12:00:00", + "endTime": "2024-184T00:00:00", + "arguments": { + } + }, + "profiles": { + "realProfiles": [ + { + "name": "/utcClock", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 1000.0 + } + } + ] + }, + { + "name": "/simple_data/b/volume", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 5.0 + } + } + ] + }, + { + "name": "/simple_data/a/volume", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 10.0 + } + } + ] + }, + { + "name": "/sink/rate", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.5, + "rate": 0.0 + } + } + ] + }, + { + "name": "/source/rate", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 1.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/sink", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 0.5 + } + } + ] + }, + { + "name": "/simple_data/a/rate", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 10.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/batterySoC", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 100.0, + "rate": 0.5 + } + } + ] + }, + { + "name": "/simple_data/b/rate", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 5.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/data/rate", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 42.0, + "rate": 0.0 + } + } + ] + }, + { + "name": "/data", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 42.0 + } + } + ] + }, + { + "name": "/simple_data/total_volume", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 0.0, + "rate": 15.0 + } + } + ] + }, + { + "name": "/source", + "schema": { + "type": "struct", + "items": { + "initial": { + "type": "real" + }, + "rate": { + "type": "real" + } + } + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": { + "initial": 100.0, + "rate": 1.0 + } + } + ] + } + ], + "discreteProfiles": [ + { + "name": "/foo", + "schema": { + "type": "real" + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": 21.0 + } + ] + }, + { + "name": "/foo/conflicted", + "schema": { + "type": "boolean" + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": false + } + ] + }, + { + "name": "/counter", + "schema": { + "type": "int" + }, + "segments": [ + { + "extent": "+00:16:40.000000", + "dynamics": 0 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 1 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 2 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 3 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 4 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 5 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 6 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 7 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 8 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 9 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 10 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 11 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 12 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 13 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 14 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 15 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 16 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 17 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 18 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 19 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 20 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 21 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 22 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 23 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 24 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 25 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 26 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 27 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 28 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 29 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 30 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 31 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 32 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 33 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 34 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 35 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 36 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 37 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 38 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 39 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 40 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 41 + }, + { + "extent": "+00:16:40.000000", + "dynamics": 42 + }, + { + "extent": "+00:03:20.000000", + "dynamics": 43 + } + ] + }, + { + "name": "/activitiesExecuted", + "schema": { + "type": "int" + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": 0 + } + ] + }, + { + "name": "/foo/starting_after_unix_epoch", + "schema": { + "type": "boolean" + }, + "segments": [ + { + "extent": "+12:00:00.000000", + "dynamics": true + } + ] + } + ] + }, + "spans": { + "simulatedActivities": [ + ], + "unfinishedActivities": [ + ] + }, + "events": { + "event": [ + ] + } +} \ No newline at end of file diff --git a/stateless-aerie/src/test/resources/temporalSubsetFooConfiguration.json b/stateless-aerie/src/test/resources/temporalSubsetFooConfiguration.json new file mode 100644 index 0000000000..59bd59231e --- /dev/null +++ b/stateless-aerie/src/test/resources/temporalSubsetFooConfiguration.json @@ -0,0 +1,6 @@ +{ + "version": "2", + "simulation_start_time": "2024-07-01T12:00:00+00:00", + "simulation_end_time": "2024-07-02T00:00:00+00:00", + "arguments": {} +} diff --git a/type-utils/build.gradle b/type-utils/build.gradle new file mode 100644 index 0000000000..b83c826453 --- /dev/null +++ b/type-utils/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + + withJavadocJar() + withSourcesJar() +} + +dependencies { + implementation project(":merlin-sdk") + + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirective.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirective.java similarity index 94% rename from merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirective.java rename to type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirective.java index 76b87e312f..461db012ab 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirective.java +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirective.java @@ -1,4 +1,4 @@ -package gov.nasa.jpl.aerie.merlin.driver; +package gov.nasa.jpl.aerie.types; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirectiveId.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirectiveId.java similarity index 52% rename from merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirectiveId.java rename to type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirectiveId.java index cc0e0d6376..c264c39dfc 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/ActivityDirectiveId.java +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/ActivityDirectiveId.java @@ -1,3 +1,3 @@ -package gov.nasa.jpl.aerie.merlin.driver; +package gov.nasa.jpl.aerie.types; public record ActivityDirectiveId(long id) {} diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelId.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/MissionModelId.java similarity index 72% rename from merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelId.java rename to type-utils/src/main/java/gov/nasa/jpl/aerie/types/MissionModelId.java index d212b7f442..eb6f31d445 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/MissionModelId.java +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/MissionModelId.java @@ -1,4 +1,4 @@ -package gov.nasa.jpl.aerie.merlin.server.models; +package gov.nasa.jpl.aerie.types; public record MissionModelId(long id) { @Override diff --git a/type-utils/src/main/java/gov/nasa/jpl/aerie/types/Plan.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/Plan.java new file mode 100644 index 0000000000..eb1efdf0e1 --- /dev/null +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/Plan.java @@ -0,0 +1,176 @@ +package gov.nasa.jpl.aerie.types; + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A representation of a grounded plan. Contains the necessary information to be simulated. + */ +public final class Plan { + // Set-once fields + private final String name; + private final MissionModelId missionModelId; + private final Timestamp startTimestamp; + private final Timestamp endTimestamp; + private final Map activityDirectives; + private final Map configuration; + + // Simulation start and end times can be freely updated + public Timestamp simulationStartTimestamp; + public Timestamp simulationEndTimestamp; + + public Plan( + final String name, + final MissionModelId missionModelId, + final Timestamp startTimestamp, + final Timestamp endTimestamp, + final Map activityDirectives + ) { + this( + name, + missionModelId, + startTimestamp, + endTimestamp, + activityDirectives, + null, + startTimestamp, + endTimestamp); + } + + public Plan( + String name, + Timestamp startTimestamp, + Timestamp endTimestamp, + Map activityDirectives, + Map simulationConfig + ) { + this( + name, + null, + startTimestamp, + endTimestamp, + activityDirectives, + simulationConfig, + startTimestamp, + endTimestamp); + } + + public Plan( + final String name, + final MissionModelId missionModelId, + final Timestamp startTimestamp, + final Timestamp endTimestamp, + final Map activityDirectives, + final Map configuration, + final Timestamp simulationStartTimestamp, + final Timestamp simulationEndTimestamp + ) { + this.name = name; + this.missionModelId = missionModelId; + this.startTimestamp = startTimestamp; + this.endTimestamp = endTimestamp; + this.activityDirectives = (activityDirectives != null) ? new HashMap<>(activityDirectives) : new HashMap<>(); + this.configuration = (configuration != null) ? new HashMap<>(configuration) : new HashMap<>(); + this.simulationStartTimestamp = simulationStartTimestamp; + this.simulationEndTimestamp = simulationEndTimestamp; + } + + public Plan(final Plan other) { + this.name = other.name; + this.missionModelId = other.missionModelId; + this.startTimestamp = other.startTimestamp; + this.endTimestamp = other.endTimestamp; + this.simulationStartTimestamp = other.simulationStartTimestamp; + this.simulationEndTimestamp = other.simulationEndTimestamp; + this.activityDirectives = new HashMap<>(other.activityDirectives); + this.configuration = new HashMap<>(other.configuration); + } + + /** + * Get the plan's name. + */ + public String name() {return name;} + + /** + * Get the id of the mission model this plan will work with. + */ + public MissionModelId missionModelId() {return missionModelId;} + + /** + * Get the start of the plan as an Instant. + */ + public Instant planStartInstant() {return startTimestamp.toInstant();} + + /** + * Get the duration of the plan. + */ + public Duration duration() { + return Duration.of(startTimestamp.microsUntil(endTimestamp), Duration.MICROSECOND); + } + + /** + * Get the map of grounded activity directives in this plan. + */ + public Map activityDirectives() {return activityDirectives;} + + /** + * Get the requested simulation configuration. + */ + public Map simulationConfiguration() {return configuration;} + + /** + * Get the requested simulation start time as an Instant. + */ + public Instant simulationStartInstant() {return simulationStartTimestamp.toInstant();} + + /** + * Get the requested simulation duration. + */ + public Duration simulationDuration() { + return Duration.of(simulationStartTimestamp.microsUntil(simulationEndTimestamp), Duration.MICROSECOND); + } + + /** + * Get the offset between the start time of the plan and the requested start time of simulation. + */ + public Duration simulationOffset() { + return Duration.of(startTimestamp.microsUntil(simulationStartTimestamp), Duration.MICROSECOND); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof final Plan other)) { + return false; + } + + return + (Objects.equals(this.name, other.name) + && Objects.equals(this.missionModelId, other.missionModelId) + && Objects.equals(this.startTimestamp, other.startTimestamp) + && Objects.equals(this.endTimestamp, other.endTimestamp) + && Objects.equals(this.activityDirectives, other.activityDirectives) + && Objects.equals(this.configuration, other.configuration) + && Objects.equals(this.simulationStartTimestamp, other.simulationStartTimestamp) + && Objects.equals(this.simulationEndTimestamp, other.simulationEndTimestamp) + ); + } + + @Override + public int hashCode() { + return Objects.hash( + name, + missionModelId, + startTimestamp, + endTimestamp, + activityDirectives, + configuration, + simulationStartTimestamp, + simulationEndTimestamp + ); + } +} diff --git a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SerializedActivity.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/SerializedActivity.java similarity index 96% rename from merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SerializedActivity.java rename to type-utils/src/main/java/gov/nasa/jpl/aerie/types/SerializedActivity.java index 712eabebe7..a861b64592 100644 --- a/merlin-driver/src/main/java/gov/nasa/jpl/aerie/merlin/driver/SerializedActivity.java +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/SerializedActivity.java @@ -1,4 +1,4 @@ -package gov.nasa.jpl.aerie.merlin.driver; +package gov.nasa.jpl.aerie.types; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; @@ -68,6 +68,6 @@ public int hashCode() { @Override public String toString() { - return "SerializedActivity { typeName = " + this.typeName + ", arguments = " + this.arguments.toString() + " }"; + return "SerializedActivity { typeName = " + this.typeName + ", arguments = " + this.arguments + " }"; } } diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Timestamp.java b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/Timestamp.java similarity index 96% rename from merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Timestamp.java rename to type-utils/src/main/java/gov/nasa/jpl/aerie/types/Timestamp.java index 16932f0fde..309c756d21 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/Timestamp.java +++ b/type-utils/src/main/java/gov/nasa/jpl/aerie/types/Timestamp.java @@ -1,4 +1,4 @@ -package gov.nasa.jpl.aerie.merlin.server.models; +package gov.nasa.jpl.aerie.types; import java.time.Instant; import java.time.LocalDateTime;