diff --git a/step-automation-packages/pom.xml b/step-automation-packages/pom.xml
index 06dd073971..53906c31fa 100644
--- a/step-automation-packages/pom.xml
+++ b/step-automation-packages/pom.xml
@@ -42,6 +42,7 @@
step-automation-packages-manager
step-automation-packages-client
step-automation-packages-controller
+ step-automation-packages-ide
diff --git a/step-automation-packages/step-automation-packages-ide/pom.xml b/step-automation-packages/step-automation-packages-ide/pom.xml
new file mode 100644
index 0000000000..db6d0906c9
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ ch.exense.step
+ step-automation-packages
+ 0.0.0-SNAPSHOT
+
+
+ step-automation-packages-ide
+
+
+ ch.exense.step
+ 21
+ 21
+ UTF-8
+
+
+
+
+ ch.exense.step
+ step-plans-base-artefacts
+ ${project.version}
+
+
+ ch.exense.step
+ step-automation-packages-yaml
+ ${project.version}
+
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ ch.exense.step
+ step-plans-core
+ ${project.version}
+ test
+
+
+ ch.exense.step
+ step-functions-plugins-jmeter-def
+ ${project.version}
+ test
+
+
+ ch.exense.step
+ step-functions-plugins-node-def
+ ${project.version}
+ test
+
+
+ ch.exense.step
+ step-automation-packages-controller
+ ${project.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java
new file mode 100644
index 0000000000..e3be980294
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (C) 2026, exense GmbH
+ *
+ * This file is part of STEP
+ *
+ * STEP is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * STEP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with STEP. If not, see .
+ ******************************************************************************/
+package step.core.collections;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import step.automation.packages.yaml.AutomationPackageYamlFragmentManager;
+import step.core.collections.inmemory.InMemoryCollectionFactory;
+import step.core.plans.Plan;
+
+public class AutomationPackageCollectionFactory implements CollectionFactory {
+
+ private final InMemoryCollectionFactory baseFactory;
+ private final AutomationPackageYamlFragmentManager fragmentManager;
+
+ public AutomationPackageCollectionFactory(Properties properties, AutomationPackageYamlFragmentManager fragmentManager) {
+ this.fragmentManager = fragmentManager;
+ this.baseFactory = new InMemoryCollectionFactory(properties);
+ }
+
+ @Override
+ public Collection getCollection(String name, Class entityClass) {
+
+ if (entityClass == Plan.class) {
+ return (Collection) new AutomationPackagePlanCollection(fragmentManager);
+ }
+
+ return baseFactory.getCollection(name, entityClass);
+ }
+
+ @Override
+ public Collection getVersionedCollection(String name) {
+ Collection baseCollection = baseFactory.getCollection(name, EntityVersion.class);
+ return baseCollection;
+ }
+
+ @Override
+ public void close() throws IOException {
+ baseFactory.close();
+ }
+}
diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java
new file mode 100644
index 0000000000..3413981cbf
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (C) 2026, exense GmbH
+ *
+ * This file is part of STEP
+ *
+ * STEP is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * STEP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with STEP. If not, see .
+ ******************************************************************************/
+package step.core.collections;
+
+import step.automation.packages.yaml.AutomationPackageYamlFragmentManager;
+import step.core.collections.inmemory.InMemoryCollection;
+import step.core.plans.Plan;
+
+public class AutomationPackagePlanCollection extends InMemoryCollection implements Collection {
+
+
+ private final AutomationPackageYamlFragmentManager fragmentManager;
+
+ public AutomationPackagePlanCollection(AutomationPackageYamlFragmentManager fragmentManager) {
+ super(true, "plan");
+ this.fragmentManager = fragmentManager;
+ super.save(fragmentManager.getPlans());
+ }
+
+ @Override
+ public Plan save(Plan p){
+ return super.save(fragmentManager.savePlan(p));
+ }
+
+ @Override
+ public void save(Iterable iterable) {
+ for (Plan p : iterable) {
+ save(p);
+ }
+ }
+
+ @Override
+ public void remove(Filter filter) {
+ find(filter, null, null, null, Integer.MAX_VALUE).forEach(fragmentManager::removePlan);
+ super.remove(filter);
+ }
+}
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java
new file mode 100644
index 0000000000..b81809bd84
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (C) 2026, exense GmbH
+ *
+ * This file is part of STEP
+ *
+ * STEP is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * STEP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with STEP. If not, see .
+ ******************************************************************************/
+package step.core.collections;
+
+import ch.exense.commons.app.Configuration;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import step.artefacts.Echo;
+import step.automation.packages.AutomationPackageHookRegistry;
+import step.automation.packages.AutomationPackageReadingException;
+import step.automation.packages.JavaAutomationPackageReader;
+import step.automation.packages.deserialization.AutomationPackageSerializationRegistry;
+import step.automation.packages.yaml.AutomationPackageYamlFragmentManager;
+import step.automation.packages.yaml.YamlAutomationPackageVersions;
+import step.core.dynamicbeans.DynamicValue;
+import step.core.plans.Plan;
+import step.parameter.ParameterManager;
+import step.parameter.automation.AutomationPackageParametersRegistration;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+public class AutomationPackageCollectionTest {
+
+ private static final Logger log = LoggerFactory.getLogger(AutomationPackageCollectionTest.class);
+
+ private final JavaAutomationPackageReader reader;
+
+ private final File sourceDirectory = new File("src/test/resources/samples/step-automation-packages-sample1");;
+ private File destinationDirectory;
+ private Collection planCollection;
+ private final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected");
+
+ public AutomationPackageCollectionTest() throws AutomationPackageReadingException {
+ AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry();
+ AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry();
+
+ // accessor is not required in this test - we only read the yaml and don't store the result anywhere
+ AutomationPackageParametersRegistration.registerParametersHooks(hookRegistry, serializationRegistry, Mockito.mock(ParameterManager.class));
+
+ this.reader = new JavaAutomationPackageReader(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, hookRegistry, serializationRegistry, new Configuration());
+ }
+
+ @Before
+ public void setUp() throws IOException, AutomationPackageReadingException {
+ Properties properties = new Properties();
+ destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile();
+ FileUtils.copyDirectory(sourceDirectory, destinationDirectory);
+
+ AutomationPackageYamlFragmentManager fragmentManager = reader.provideAutomationPackageYamlFragmentManager(destinationDirectory);
+ AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(properties, fragmentManager);
+ planCollection = collectionFactory.getCollection("plan", Plan.class);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ FileUtils.deleteDirectory(destinationDirectory);
+ }
+
+ @Test
+ public void testReadAllPlans() {
+ long count = planCollection.count(Filters.empty(), 100);
+ List plans = planCollection.find(Filters.empty(), null, null, null, 100).collect(Collectors.toList());
+
+ assertEquals(2, count);
+ Set names = plans.stream().map(p -> p.getAttributes().get("name")).collect(Collectors.toUnmodifiableSet());
+
+ assertEquals(2, names.size());
+
+ assertTrue(names.contains("Test Plan"));
+ assertTrue(names.contains("Test Plan with Composite"));
+ }
+
+ @Test
+ public void testPlanModify() throws IOException {
+ Optional optionalPlan = planCollection.find(Filters.equals("attributes.name", "Test Plan"), null, null, null, 100).findFirst();
+
+ assertTrue(optionalPlan.isPresent());
+
+ Plan plan = optionalPlan.get();
+
+ Echo firstEcho = (Echo) plan.getRoot().getChildren().get(0);
+ DynamicValue text = firstEcho.getText();
+ text.setDynamic(true);
+ text.setExpression("new Date().toString();");
+
+ planCollection.save(plan);
+
+ assertFilesEqual(expectedFilesPath.resolve("plan1AfterModification.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"));
+ }
+
+
+ @Test
+ public void testPlanRenameExisting() throws IOException {
+ Optional optionalPlan = planCollection.find(Filters.equals("attributes.name", "Test Plan"), null, null, null, 100).findFirst();
+
+ assertTrue(optionalPlan.isPresent());
+
+ Plan plan = optionalPlan.get();
+
+ plan.getAttributes().put("name", "New Plan Name");
+
+ planCollection.save(plan);
+
+ assertFilesEqual(expectedFilesPath.resolve("plan1AfterRename.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"));
+ }
+
+
+ @Test
+ public void testPlanRemoveExisting() throws IOException {
+ planCollection.remove(Filters.equals("attributes.name", "Test Plan"));
+
+ assertFilesEqual(expectedFilesPath.resolve("plan1AfterRemove.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"));
+ }
+
+ private void assertFilesEqual(Path expected, Path actual) throws IOException {
+ List expectedLines = Files.readAllLines(expected);
+ List actualLines = Files.readAllLines(actual);
+
+ assertEquals(expectedLines, actualLines);
+ }
+}
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/.apignore b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/.apignore
new file mode 100644
index 0000000000..319325c32d
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/.apignore
@@ -0,0 +1,2 @@
+/ignored
+/ignoredFile.yml
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml
new file mode 100644
index 0000000000..13510fa65a
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml
@@ -0,0 +1,9 @@
+schemaVersion: 1.0.0
+name: "My package"
+fragments:
+ - "keywords.yml"
+ - "plans/*.yml"
+ - "schedules.yml"
+ - "parameters.yml"
+ - "parameters2.yml"
+ - "unknown.yml"
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml
new file mode 100644
index 0000000000..642a8f1072
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml
@@ -0,0 +1,30 @@
+---
+fragments: []
+keywords: []
+plans:
+- version: "1.2.0"
+ name: "Test Plan"
+ root:
+ testCase:
+ nodeName: "Test Plan"
+ children:
+ - echo:
+ text:
+ expression: "new Date().toString();"
+ - echo:
+ text:
+ expression: "mySimpleKey"
+ - callKeyword:
+ nodeName: "CallMyKeyword2"
+ inputs:
+ - myInput: "myValue"
+ keyword: "MyKeyword2"
+ agents: null
+ categories:
+ - "Yaml Plan"
+plansPlainText:
+- name: "Plain text plan"
+ rootType: "Sequence"
+ categories:
+ - "PlainTextPlan"
+ file: "plans/plan2.plan"
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml
new file mode 100644
index 0000000000..af434784e2
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml
@@ -0,0 +1,10 @@
+---
+fragments: []
+keywords: []
+plans: []
+plansPlainText:
+- name: "Plain text plan"
+ rootType: "Sequence"
+ categories:
+ - "PlainTextPlan"
+ file: "plans/plan2.plan"
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml
new file mode 100644
index 0000000000..8cd5bc5d57
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml
@@ -0,0 +1,29 @@
+---
+fragments: []
+keywords: []
+plans:
+- version: "1.2.0"
+ name: "New Plan Name"
+ root:
+ testCase:
+ nodeName: "Test Plan"
+ children:
+ - echo:
+ text: "Just echo"
+ - echo:
+ text:
+ expression: "mySimpleKey"
+ - callKeyword:
+ nodeName: "CallMyKeyword2"
+ inputs:
+ - myInput: "myValue"
+ keyword: "MyKeyword2"
+ agents: null
+ categories:
+ - "Yaml Plan"
+plansPlainText:
+- name: "Plain text plan"
+ rootType: "Sequence"
+ categories:
+ - "PlainTextPlan"
+ file: "plans/plan2.plan"
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/ignoredFile.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/ignoredFile.yml
new file mode 100644
index 0000000000..06f858207b
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/ignoredFile.yml
@@ -0,0 +1 @@
+#I should be ignored
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/keywords.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/keywords.yml
new file mode 100644
index 0000000000..5d33a14488
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/keywords.yml
@@ -0,0 +1,30 @@
+keywords:
+ - JMeter:
+ name: "JMeter keyword from automation package"
+ description: "JMeter keyword 1"
+ executeLocally: false
+ useCustomTemplate: true
+ callTimeout: 1000
+ jmeterTestplan: "jmeterProject1/jmeterProject1.xml"
+ - Composite:
+ name: "Composite keyword from AP"
+ plan:
+ root:
+ testCase:
+ children:
+ - echo:
+ text: "Just echo"
+ - return:
+ output:
+ - output1: "value"
+ - output2:
+ expression: "'some thing dynamic'"
+ - GeneralScript:
+ name: "GeneralScript keyword from AP"
+ scriptLanguage: javascript
+ scriptFile: "jsProject/jsSample.js"
+ librariesFile: "lib/fakeLib.jar"
+ - Node:
+ name: "NodeAutomation"
+ jsfile: "nodeProject/nodeSample.ts"
+
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters.yml
new file mode 100644
index 0000000000..587e90b308
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters.yml
@@ -0,0 +1,14 @@
+parameters:
+ - key: myKey
+ value: myValue
+ description: some description
+ activationScript: abc
+ priority: 10
+ protectedValue: true
+ scope: APPLICATION
+ scopeEntity: entity
+ - key: mySimpleKey
+ value: mySimpleValue
+ - key: myDynamicParam
+ value:
+ expression: "mySimpleKey"
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters2.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters2.yml
new file mode 100644
index 0000000000..8754e85903
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters2.yml
@@ -0,0 +1,9 @@
+parameters:
+ - key: myKey2
+ value: myValue2
+ description: some description 2
+ activationScript: abc
+ priority: 10
+ protectedValue: true
+ scope: APPLICATION
+ scopeEntity: entity
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plan.plan b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plan.plan
new file mode 100644
index 0000000000..66b7ca6d84
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plan.plan
@@ -0,0 +1,3 @@
+Sequence
+Echo "Testing annotated plan"
+End
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan1.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan1.yml
new file mode 100644
index 0000000000..23db1ca1a7
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan1.yml
@@ -0,0 +1,26 @@
+---
+fragments: []
+keywords: []
+plans:
+- name: "Test Plan"
+ root:
+ testCase:
+ children:
+ - echo:
+ text: "Just echo"
+ - echo:
+ text:
+ expression: "mySimpleKey"
+ - callKeyword:
+ nodeName: "CallMyKeyword2"
+ inputs:
+ - myInput: "myValue"
+ keyword: "MyKeyword2"
+ categories:
+ - "Yaml Plan"
+plansPlainText:
+- name: "Plain text plan"
+ rootType: "Sequence"
+ categories:
+ - "PlainTextPlan"
+ file: "plans/plan2.plan"
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.plan b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.plan
new file mode 100644
index 0000000000..66b7ca6d84
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.plan
@@ -0,0 +1,3 @@
+Sequence
+Echo "Testing annotated plan"
+End
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.yml
new file mode 100644
index 0000000000..2a576d02da
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.yml
@@ -0,0 +1,17 @@
+plans:
+ - name: Test Plan with Composite
+ categories:
+ - Yaml Plan
+ - Composite
+ root:
+ testCase:
+ children:
+ - echo:
+ text: "Calling composite"
+ - callKeyword:
+ keyword: "Composite keyword from AP"
+ children:
+ - check:
+ expression: "output.output1.equals('value')"
+ - check:
+ expression: "output.output2.equals('some thing dynamic')"
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/firstPlainText.plan b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/firstPlainText.plan
new file mode 100644
index 0000000000..e9dd736886
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/firstPlainText.plan
@@ -0,0 +1,3 @@
+Sequence
+Echo "First plain text plan"
+End
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/secondPlainText.plan b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/secondPlainText.plan
new file mode 100644
index 0000000000..00c3bacb0d
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/secondPlainText.plan
@@ -0,0 +1,3 @@
+Sequence
+Echo "Second plain text plan"
+End
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/schedules.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/schedules.yml
new file mode 100644
index 0000000000..6d95ed7161
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/schedules.yml
@@ -0,0 +1,7 @@
+schedules:
+ - name: "firstSchedule"
+ cron: "0 15 10 ? * *"
+ cronExclusions:
+ - "0 0 9 25 * ?"
+ - "0 0 9 20 * ?"
+ planName: "Test Plan"
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/unknown.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/unknown.yml
new file mode 100644
index 0000000000..d378e6078d
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/unknown.yml
@@ -0,0 +1,3 @@
+unknown:
+ - someFieldA: valueA
+ someFieldB: valueB
\ No newline at end of file
diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageReader.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageReader.java
index 37c29ec1a7..85d3f89a9f 100644
--- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageReader.java
+++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageReader.java
@@ -24,6 +24,7 @@
import step.automation.packages.model.ScriptAutomationPackageKeyword;
import step.automation.packages.yaml.AutomationPackageDescriptorReader;
import step.automation.packages.deserialization.AutomationPackageSerializationRegistry;
+import step.automation.packages.yaml.AutomationPackageYamlFragmentManager;
import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml;
import step.automation.packages.yaml.model.AutomationPackageFragmentYaml;
import step.core.plans.Plan;
@@ -98,23 +99,18 @@ public AutomationPackageContent readAutomationPackage(T automationPackageArchive
* can be read as {@link step.engine.plugins.LocalFunctionPlugin.LocalFunction}, but not as {@link GeneralScriptFunction}
* @param scanAnnotations true if it is required to include annotated java keywords and plans as well as located in yaml descriptor
*/
- public AutomationPackageContent readAutomationPackage(T automationPackageArchive, String apVersion, boolean isClasspathBased, boolean scanAnnotations) throws AutomationPackageReadingException {
- try {
- if (automationPackageArchive.hasAutomationPackageDescriptor()) {
- try (InputStream yamlInputStream = automationPackageArchive.getDescriptorYaml()) {
- AutomationPackageDescriptorYaml descriptorYaml = getOrCreateDescriptorReader().readAutomationPackageDescriptor(yamlInputStream, automationPackageArchive.getOriginalFileName());
- return buildAutomationPackage(descriptorYaml, automationPackageArchive, apVersion, isClasspathBased, scanAnnotations);
- }
- } else if (scanAnnotations) {
- return buildAutomationPackage(null, automationPackageArchive, apVersion, isClasspathBased, scanAnnotations);
- } else {
- return null;
- }
- } catch (IOException ex) {
- throw new AutomationPackageReadingException("Unable to read the automation package", ex);
+ public AutomationPackageContent readAutomationPackage(T archive, String apVersion, boolean isClasspathBased, boolean scanAnnotations) throws AutomationPackageReadingException {
+ if (archive.hasAutomationPackageDescriptor()) {
+ AutomationPackageDescriptorYaml descriptorYaml = getOrCreateDescriptorReader().readAutomationPackageDescriptor(archive.getDescriptorYamlUrl(), archive.getOriginalFileName());
+ return buildAutomationPackage(descriptorYaml, archive, apVersion, isClasspathBased, scanAnnotations);
+ } else if (scanAnnotations) {
+ return buildAutomationPackage(null, archive, apVersion, isClasspathBased, scanAnnotations);
+ } else {
+ return null;
}
}
+
protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescriptorYaml descriptor, T archive, String apVersion,
boolean isClasspathBased, boolean scanAnnotations) throws AutomationPackageReadingException {
AutomationPackageContent res = newContentInstance();
@@ -128,11 +124,13 @@ protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescr
// apply imported fragments recursively
if (descriptor != null) {
+ readAutomationPackageYamlFragmentTree(archive, descriptor);
fillAutomationPackageWithImportedFragments(res, descriptor, archive);
}
return res;
}
+
private String resolveName(AutomationPackageDescriptorYaml descriptor, T archive) throws AutomationPackageReadingException {
String finalName;
if (descriptor != null) {
@@ -179,27 +177,45 @@ protected AutomationPackageContent newContentInstance(){
abstract protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(T archive, boolean isClasspathBased, AutomationPackageContent res) throws AutomationPackageReadingException;
- public void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive) throws AutomationPackageReadingException {
- fillContentSections(targetPackage, fragment, archive);
- if (!fragment.getFragments().isEmpty()) {
- for (String importedFragmentReference : fragment.getFragments()) {
+ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragmentManager(T archive) throws AutomationPackageReadingException {
+ AutomationPackageDescriptorReader reader = getOrCreateDescriptorReader();
+ AutomationPackageDescriptorYaml descriptor = reader.readAutomationPackageDescriptor(archive.getDescriptorYamlUrl(), archive.getOriginalFileName());
+ readAutomationPackageYamlFragmentTree(archive, descriptor);
+ return new AutomationPackageYamlFragmentManager(descriptor, getOrCreateDescriptorReader());
+ }
+
+ private void readAutomationPackageYamlFragmentTree(AutomationPackageArchive archive, AutomationPackageFragmentYaml parent) throws AutomationPackageReadingException {
+
+ if (!parent.getFragments().isEmpty()) {
+ for (String importedFragmentReference : parent.getFragments()) {
List resources = archive.getResourcesByPattern(importedFragmentReference);
for (URL resource : resources) {
- try (InputStream fragmentYamlStream = resource.openStream()) {
- fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, importedFragmentReference, archive.getOriginalFileName());
- fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive);
- } catch (IOException e) {
- throw new AutomationPackageReadingException("Unable to read fragment in automation package: " + importedFragmentReference, e);
- }
+ AutomationPackageFragmentYaml fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(resource, archive.getOriginalFileName());
+ fragment.setParent(parent);
+ parent.getChildren().add(fragment);
+ readAutomationPackageYamlFragmentTree(archive, fragment);
}
}
}
}
+ private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive) throws AutomationPackageReadingException {
+ fillContentSections(targetPackage, fragment, archive);
+
+ for (AutomationPackageFragmentYaml child: fragment.getChildren()) {
+ fillAutomationPackageWithImportedFragments(targetPackage, child, archive);
+ }
+ }
+
protected void fillContentSections(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive) throws AutomationPackageReadingException {
targetPackage.getKeywords().addAll(fragment.getKeywords());
- targetPackage.getPlans().addAll(fragment.getPlans().stream().map(p -> getOrCreateDescriptorReader().getPlanReader().yamlPlanToPlan(p)).collect(Collectors.toList()));
+ targetPackage.getPlans().addAll(fragment.getPlans().stream().map(p -> {
+ Plan plan = getOrCreateDescriptorReader().getPlanReader().yamlPlanToPlan(p);
+ plan.getAttributes().put("fragmentUrl", fragment.getFragmentUrl().toString());
+ plan.getAttributes().put("nameInYaml", p.getName());
+ return plan;
+ }).collect(Collectors.toList()));
readPlainTextPlans(targetPackage, fragment, archive);
diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/JavaAutomationPackageReader.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/JavaAutomationPackageReader.java
index 2114924926..88c02feebe 100644
--- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/JavaAutomationPackageReader.java
+++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/JavaAutomationPackageReader.java
@@ -4,6 +4,7 @@
import org.apache.commons.lang3.StringUtils;
import step.automation.packages.deserialization.AutomationPackageSerializationRegistry;
import step.automation.packages.model.ScriptAutomationPackageKeyword;
+import step.automation.packages.yaml.AutomationPackageYamlFragmentManager;
import step.core.accessors.AbstractOrganizableObject;
import step.core.dynamicbeans.DynamicValue;
import step.core.plans.Plan;
@@ -22,7 +23,10 @@
import step.plugins.functions.types.CompositeFunctionUtils;
import step.plugins.java.GeneralScriptFunction;
-import java.io.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
@@ -253,4 +257,17 @@ public AutomationPackageContent readAutomationPackageFromJarFile(File automation
throw new AutomationPackageReadingException("IO Exception", e);
}
}
+
+ /** Convenient method for test
+ * @param automationPackage the JAR file to be read
+ * @return the automation package content raed from the provided files
+ * @throws AutomationPackageReadingException in case of error
+ */
+ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragmentManager(File automationPackage) throws AutomationPackageReadingException {
+ try (JavaAutomationPackageArchive automationPackageArchive = new JavaAutomationPackageArchive(automationPackage, null, null)) {
+ return provideAutomationPackageYamlFragmentManager(automationPackageArchive);
+ } catch (IOException e) {
+ throw new AutomationPackageReadingException("IO Exception", e);
+ }
+ }
}
diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java
index 968500bafd..7a65211340 100644
--- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java
+++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java
@@ -42,6 +42,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
@@ -70,25 +71,35 @@ public AutomationPackageDescriptorReader(String jsonSchemaPath, AutomationPackag
}
}
- public AutomationPackageDescriptorYaml readAutomationPackageDescriptor(InputStream yamlDescriptor, String packageFileName) throws AutomationPackageReadingException {
+ public AutomationPackageDescriptorYaml readAutomationPackageDescriptor(URL resource, String packageFileName) throws AutomationPackageReadingException {
log.info("Reading automation package descriptor...");
- return readAutomationPackageYamlFile(yamlDescriptor, getDescriptorClass(), packageFileName);
+ return readAutomationPackageYamlFile(resource, getDescriptorClass(), packageFileName);
+ }
+
+ public AutomationPackageDescriptorYaml readAutomationPackageDescriptor(InputStream yaml, String packageFileName) throws AutomationPackageReadingException {
+ log.info("Reading automation package descriptor...");
+ return readAutomationPackageYamlFile(yaml, getDescriptorClass(), packageFileName);
}
protected Class extends AutomationPackageDescriptorYaml> getDescriptorClass() {
return AutomationPackageDescriptorYamlImpl.class;
}
- public AutomationPackageFragmentYaml readAutomationPackageFragment(InputStream yamlFragment, String fragmentName, String packageFileName) throws AutomationPackageReadingException {
+ public AutomationPackageFragmentYaml readAutomationPackageFragment(URL resource, String packageFileName) throws AutomationPackageReadingException {
+ log.info("Reading automation package descriptor fragment ({})...", resource);
+ return readAutomationPackageYamlFile(resource, getFragmentClass(), packageFileName);
+ }
+
+ public AutomationPackageFragmentYaml readAutomationPackageFragment(InputStream yaml, String fragmentName, String packageFileName) throws AutomationPackageReadingException {
log.info("Reading automation package descriptor fragment ({})...", fragmentName);
- return readAutomationPackageYamlFile(yamlFragment, getFragmentClass(), packageFileName);
+ return readAutomationPackageYamlFile(yaml, getFragmentClass(), packageFileName);
}
protected Class extends AutomationPackageFragmentYaml> getFragmentClass() {
return AutomationPackageFragmentYamlImpl.class;
}
- protected T readAutomationPackageYamlFile(InputStream yaml, Class targetClass, String packageFileName) throws AutomationPackageReadingException {
+ private T readAutomationPackageYamlFile(InputStream yaml, Class targetClass, String packageFileName) throws AutomationPackageReadingException {
try {
String yamlDescriptorString = new String(yaml.readAllBytes(), StandardCharsets.UTF_8);
String version = null;
@@ -108,6 +119,17 @@ protected T readAutomationPackageYamlF
T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(yamlDescriptorString, targetClass);
logAfterRead(packageFileName, res);
+
+ return res;
+ } catch (IOException | YamlPlanValidationException e) {
+ throw new AutomationPackageReadingException("Unable to read the automation package yaml. Caused by: " + e.getMessage(), e);
+ }
+ }
+
+ private T readAutomationPackageYamlFile(URL resource, Class targetClass, String packageFileName) throws AutomationPackageReadingException {
+ try (InputStream yaml = resource.openStream()) {
+ T res = readAutomationPackageYamlFile(yaml, targetClass, packageFileName);
+ res.setFragmentUrl(resource);
return res;
} catch (IOException | YamlPlanValidationException e) {
throw new AutomationPackageReadingException("Unable to read the automation package yaml. Caused by: " + e.getMessage(), e);
@@ -143,7 +165,7 @@ protected String readJsonSchema(String jsonSchemaPath) {
}
}
- protected ObjectMapper createYamlObjectMapper() {
+ private ObjectMapper createYamlObjectMapper() {
YAMLFactory yamlFactory = new YAMLFactory();
// Disable native type id to enable conversion to generic Documents
@@ -169,7 +191,11 @@ protected ObjectMapper createYamlObjectMapper() {
return yamlMapper;
}
- public YamlPlanReader getPlanReader(){
+ public ObjectMapper getYamlObjectMapper() {
+ return yamlObjectMapper;
+ }
+
+ public YamlPlanReader getPlanReader() {
return this.planReader;
}
diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java
new file mode 100644
index 0000000000..803fbfdfee
--- /dev/null
+++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2026, exense GmbH
+ *
+ * This file is part of STEP
+ *
+ * STEP is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * STEP is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with STEP. If not, see .
+ ******************************************************************************/
+package step.automation.packages.yaml;
+
+import com.fasterxml.jackson.core.exc.StreamWriteException;
+import com.fasterxml.jackson.databind.DatabindException;
+import org.yaml.snakeyaml.Yaml;
+import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml;
+import step.automation.packages.yaml.model.AutomationPackageFragmentYaml;
+import step.core.plans.Plan;
+import step.plans.parser.yaml.YamlPlan;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+public class AutomationPackageYamlFragmentManager {
+
+
+ private final AutomationPackageDescriptorReader descriptorReader;
+
+ private final Map planToYamlPlan = new ConcurrentHashMap<>();
+ private final Map planToYamlFragment = new ConcurrentHashMap<>();
+
+ public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, AutomationPackageDescriptorReader descriptorReader) {
+
+ this.descriptorReader = descriptorReader;
+
+ initializeMaps(descriptorYaml);
+ }
+
+ public void initializeMaps(AutomationPackageFragmentYaml fragment) {
+ for (YamlPlan p: fragment.getPlans()) {
+ Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(p);
+ planToYamlPlan.put(plan, p);
+ planToYamlFragment.put(plan, fragment);
+ };
+
+ for (AutomationPackageFragmentYaml child : fragment.getChildren()) {
+ initializeMaps(child);
+ }
+ }
+
+ public Iterable getPlans() {
+ return planToYamlPlan.keySet();
+ }
+
+ public Plan savePlan(Plan p) {
+ YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p);
+
+ AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p);
+ if (fragment == null) {
+ fragment = newFragmentForPlan(p);
+ fragment.getPlans().add(newYamlPlan);
+ } else {
+ YamlPlan yamlPlan = planToYamlPlan.get(p);
+ fragment.getPlans().replaceAll(plan -> plan == yamlPlan ? newYamlPlan : plan);
+ }
+
+ planToYamlPlan.put(p, newYamlPlan);
+ writeFragment(fragment);
+ return p;
+ }
+
+ private AutomationPackageFragmentYaml newFragmentForPlan(Plan p) {
+
+ throw new UnsupportedOperationException("new Plan creation not yet supported in IDE");
+ /*
+ try {
+ File file = new File(descriptorYaml.getFragmentUrl().toURI());
+
+ Path file.toPath().getParent().resolveSibling(getRelativePathForNewPlan(p));
+
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }*/
+ }
+
+ public void removePlan(Plan p) {
+ AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p);
+ YamlPlan yamlPlan = planToYamlPlan.get(p);
+
+ fragment.getPlans().remove(yamlPlan);
+
+ planToYamlPlan.remove(p);
+ planToYamlFragment.remove(p);
+
+ writeFragment(fragment);
+ }
+
+ private void writeFragment(AutomationPackageFragmentYaml fragment) {
+ try {
+ File file = new File(fragment.getFragmentUrl().toURI());
+ descriptorReader.getYamlObjectMapper().writeValue(file, fragment);
+ } catch (URISyntaxException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AbstractAutomationPackageFragmentYaml.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AbstractAutomationPackageFragmentYaml.java
index 022ce163b5..82b486e780 100644
--- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AbstractAutomationPackageFragmentYaml.java
+++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AbstractAutomationPackageFragmentYaml.java
@@ -25,7 +25,9 @@
import step.plans.automation.YamlPlainTextPlan;
import step.plans.parser.yaml.YamlPlan;
+import java.net.URL;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -38,6 +40,15 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio
@JsonIgnore
private Map> additionalFields;
+ @JsonIgnore
+ private URL url;
+
+ @JsonIgnore
+ private List children = new LinkedList<>();
+
+ @JsonIgnore
+ private AutomationPackageFragmentYaml parent;
+
@Override
public List getKeywords() {
return keywords;
@@ -86,4 +97,29 @@ public List getPlansPlainText() {
public void setPlansPlainText(List plansPlainText) {
this.plansPlainText = plansPlainText;
}
+
+ @JsonIgnore
+ public void setFragmentUrl(URL url) {
+ this.url = url;
+ }
+
+ @JsonIgnore
+ public URL getFragmentUrl() {
+ return url;
+ }
+
+ @JsonIgnore
+ public List getChildren() {
+ return children;
+ }
+
+ @JsonIgnore
+ public AutomationPackageFragmentYaml getParent() {
+ return parent;
+ }
+
+ @JsonIgnore
+ public void setParent(AutomationPackageFragmentYaml parent) {
+ this.parent = parent;
+ }
}
diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYaml.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYaml.java
index a141f5e090..e9ad660af9 100644
--- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYaml.java
+++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYaml.java
@@ -22,6 +22,7 @@
import step.plans.automation.YamlPlainTextPlan;
import step.plans.parser.yaml.YamlPlan;
+import java.net.URL;
import java.util.List;
import java.util.Map;
@@ -40,4 +41,14 @@ public interface AutomationPackageFragmentYaml {
default List getAdditionalField(String k) {
return (List) getAdditionalFields().get(k);
}
+
+ URL getFragmentUrl();
+
+ void setFragmentUrl(URL url);
+
+ List getChildren();
+
+ AutomationPackageFragmentYaml getParent();
+
+ void setParent(AutomationPackageFragmentYaml parent);
}
diff --git a/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java b/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java
index da33cac6c0..1dd6326f8b 100644
--- a/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java
+++ b/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java
@@ -68,6 +68,8 @@ public String getAutomationPackageName() {
abstract public boolean hasAutomationPackageDescriptor();
+ abstract public URL getDescriptorYamlUrl();
+
abstract public InputStream getDescriptorYaml();
abstract public InputStream getResourceAsStream(String resourcePath) throws IOException;
diff --git a/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java b/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java
index 6cfe9f89a1..4cddc605dc 100644
--- a/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java
+++ b/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java
@@ -102,6 +102,17 @@ public boolean hasAutomationPackageDescriptor() {
return false;
}
+ @Override
+ public URL getDescriptorYamlUrl() {
+ for (String metadataFile : METADATA_FILES) {
+ URL yamlDescriptor = classLoaderForMainApFile.getResource(metadataFile);
+ if (yamlDescriptor != null) {
+ return yamlDescriptor;
+ }
+ }
+ return null;
+ }
+
@Override
public InputStream getDescriptorYaml() {
for (String metadataFile : METADATA_FILES) {
diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/YamlPlanReader.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/YamlPlanReader.java
index 25a19a1b8e..bfa9ffbae9 100644
--- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/YamlPlanReader.java
+++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/YamlPlanReader.java
@@ -271,7 +271,7 @@ public Plan yamlPlanToPlan(YamlPlan yamlPlan) {
return plan;
}
- protected YamlPlan planToYamlPlan(Plan plan){
+ public YamlPlan planToYamlPlan(Plan plan){
YamlPlan yamlPlan = new YamlPlan();
yamlPlan.setName(plan.getAttribute(AbstractOrganizableObject.NAME));
yamlPlan.setVersion(currentVersion.toString());