From 58e52f02d0bde1bed1a558563bed57599262e854 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 6 Feb 2026 11:09:06 +0100 Subject: [PATCH 01/15] SED-4429 initial draft of mapping fragments to plans --- step-automation-packages/pom.xml | 1 + .../step-automation-packages-ide/pom.xml | 60 +++++++ .../AutomationPackageCollectionFactory.java | 58 +++++++ .../AutomationPackagePlanCollection.java | 53 ++++++ .../AutomationPackageCollectionTest.java | 162 ++++++++++++++++++ .../.apignore | 2 + .../automation-package.yml | 9 + .../expected/plan1AfterModification.yml | 30 ++++ .../expected/plan1AfterRemove.yml | 10 ++ .../expected/plan1AfterRename.yml | 29 ++++ .../ignoredFile.yml | 1 + .../keywords.yml | 30 ++++ .../parameters.yml | 14 ++ .../parameters2.yml | 9 + .../plan.plan | 3 + .../plans/plan1.yml | 26 +++ .../plans/plan2.plan | 3 + .../plans/plan2.yml | 17 ++ .../plansPlainText/firstPlainText.plan | 3 + .../plansPlainText/secondPlainText.plan | 3 + .../schedules.yml | 7 + .../unknown.yml | 3 + .../packages/AutomationPackageReader.java | 66 ++++--- .../packages/JavaAutomationPackageReader.java | 15 ++ .../AutomationPackageDescriptorReader.java | 40 ++++- .../AutomationPackageYamlFragmentManager.java | 119 +++++++++++++ ...AbstractAutomationPackageFragmentYaml.java | 36 ++++ .../model/AutomationPackageFragmentYaml.java | 11 ++ .../packages/AutomationPackageArchive.java | 2 + .../JavaAutomationPackageArchive.java | 11 ++ .../plans/parser/yaml/YamlPlanReader.java | 2 +- 31 files changed, 802 insertions(+), 33 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/pom.xml create mode 100644 step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java create mode 100644 step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/.apignore create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/ignoredFile.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/keywords.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters2.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plan.plan create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan1.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.plan create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/firstPlainText.plan create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/secondPlainText.plan create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/schedules.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/unknown.yml create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java 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..6ecaa21148 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/pom.xml @@ -0,0 +1,60 @@ + + + 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-ide + ${project.version} + + + ch.exense.step + step-automation-packages-controller + ${project.version} + + + ch.exense.step + step-plans-base-artefacts + ${project.version} + + + ch.exense.step + step-automation-packages-yaml + ${project.version} + + + ch.exense.step + step-controller-backend + ${project.version} + + + org.mockito + mockito-core + test + + + org.mockito + mockito-all + 1.9.5 + 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..b955ec060f --- /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, 100).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..de21189ae9 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * 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.scheduler.AutomationPackageSchedulerHook; +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; +import step.automation.packages.yaml.YamlAutomationPackageVersions; +import step.core.dynamicbeans.DynamicValue; +import step.core.plans.Plan; +import step.core.scheduler.ExecutionScheduler; +import step.core.scheduler.automation.AutomationPackageSchedule; +import step.core.scheduler.automation.AutomationPackageScheduleRegistration; +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(); + + AutomationPackageScheduleRegistration.registerSerialization(serializationRegistry); + + hookRegistry.register(AutomationPackageSchedule.FIELD_NAME_IN_AP, new AutomationPackageSchedulerHook(Mockito.mock(ExecutionScheduler.class))); + + // 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 8e2b5ea70f..05c5b54aad 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 isLocalPackage, 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, isLocalPackage, scanAnnotations); - } - } else if (scanAnnotations) { - return buildAutomationPackage(null, automationPackageArchive, apVersion, isLocalPackage, 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 isLocalPackage, boolean scanAnnotations) throws AutomationPackageReadingException { + if (archive.hasAutomationPackageDescriptor()) { + AutomationPackageDescriptorYaml descriptorYaml = getOrCreateDescriptorReader().readAutomationPackageDescriptor(archive.getDescriptorYamlUrl(), archive.getOriginalFileName()); + return buildAutomationPackage(descriptorYaml, archive, apVersion, isLocalPackage, scanAnnotations); + } else if (scanAnnotations) { + return buildAutomationPackage(null, archive, apVersion, isLocalPackage, scanAnnotations); + } else { + return null; } } + protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescriptorYaml descriptor, T archive, String apVersion, boolean isLocalPackage, 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 isLocalPackage, 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 39847ecf30..9f393ffc1f 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,8 @@ 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.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.core.accessors.AbstractOrganizableObject; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; @@ -253,4 +255,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 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 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()); From fbad6b5d7d594088698e0dcf93ff45d72214e994 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 12 Feb 2026 09:59:05 +0100 Subject: [PATCH 02/15] SED-4429 fix: remove unused dependencies, gemini code review inputs --- .../step-automation-packages-ide/pom.xml | 26 ------------------- .../AutomationPackagePlanCollection.java | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/step-automation-packages/step-automation-packages-ide/pom.xml b/step-automation-packages/step-automation-packages-ide/pom.xml index 6ecaa21148..9aa4ac857b 100644 --- a/step-automation-packages/step-automation-packages-ide/pom.xml +++ b/step-automation-packages/step-automation-packages-ide/pom.xml @@ -19,16 +19,6 @@ - - ch.exense.step - step-ide - ${project.version} - - - ch.exense.step - step-automation-packages-controller - ${project.version} - ch.exense.step step-plans-base-artefacts @@ -39,22 +29,6 @@ step-automation-packages-yaml ${project.version} - - ch.exense.step - step-controller-backend - ${project.version} - - - org.mockito - mockito-core - test - - - org.mockito - mockito-all - 1.9.5 - test - \ No newline at end of file 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 index b955ec060f..3413981cbf 100644 --- 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 @@ -47,7 +47,7 @@ public void save(Iterable iterable) { @Override public void remove(Filter filter) { - find(filter, null, null, null, 100).forEach(fragmentManager::removePlan); + find(filter, null, null, null, Integer.MAX_VALUE).forEach(fragmentManager::removePlan); super.remove(filter); } } From 0a7306abb00e951140b6fa557b6838bbfc1e6101 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 12 Feb 2026 11:32:41 +0100 Subject: [PATCH 03/15] SED-4429 fix: dependencies --- .../step-automation-packages-ide/pom.xml | 31 +++++++++++++++++++ .../AutomationPackageCollectionTest.java | 10 ------ .../packages/JavaAutomationPackageReader.java | 6 ++-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/step-automation-packages/step-automation-packages-ide/pom.xml b/step-automation-packages/step-automation-packages-ide/pom.xml index 9aa4ac857b..db6d0906c9 100644 --- a/step-automation-packages/step-automation-packages-ide/pom.xml +++ b/step-automation-packages/step-automation-packages-ide/pom.xml @@ -29,6 +29,37 @@ 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/test/java/step/core/collections/AutomationPackageCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java index de21189ae9..b81809bd84 100644 --- 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 @@ -31,14 +31,10 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.automation.packages.scheduler.AutomationPackageSchedulerHook; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.automation.packages.yaml.YamlAutomationPackageVersions; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; -import step.core.scheduler.ExecutionScheduler; -import step.core.scheduler.automation.AutomationPackageSchedule; -import step.core.scheduler.automation.AutomationPackageScheduleRegistration; import step.parameter.ParameterManager; import step.parameter.automation.AutomationPackageParametersRegistration; @@ -56,8 +52,6 @@ public class AutomationPackageCollectionTest { - - private static final Logger log = LoggerFactory.getLogger(AutomationPackageCollectionTest.class); private final JavaAutomationPackageReader reader; @@ -71,10 +65,6 @@ public AutomationPackageCollectionTest() throws AutomationPackageReadingExceptio AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); - AutomationPackageScheduleRegistration.registerSerialization(serializationRegistry); - - hookRegistry.register(AutomationPackageSchedule.FIELD_NAME_IN_AP, new AutomationPackageSchedulerHook(Mockito.mock(ExecutionScheduler.class))); - // 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)); 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 9f393ffc1f..b3afde5a0b 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 @@ -5,7 +5,6 @@ import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.ScriptAutomationPackageKeyword; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; -import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.core.accessors.AbstractOrganizableObject; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; @@ -24,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; From 7911b28c6a5cc298ff0b5967b52cee6ce4ec58f7 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 13 Mar 2026 14:01:29 +0100 Subject: [PATCH 04/15] SED-4539 basic plan add, remove, modify --- .../AutomationPackagePlanCollection.java | 4 +- .../AutomationPackageCollectionTest.java | 109 ++++++++- .../automation-package.yml | 13 ++ .../expected/Hello_World_Plan.yml | 11 + .../expected/descriptorAfterAdd.yml | 31 +++ .../expected/plan1AfterAdd.yml | 35 +++ .../expected/plan1AfterModifyAndAdd.yml | 39 ++++ .../expected/plan1AfterRemove.yml | 1 - .../plans/plan1.yml | 2 +- .../AutomationPackageDescriptorReader.java | 16 +- .../AutomationPackageYamlFragmentManager.java | 208 +++++++++++++++--- .../AdditionalFieldHandler.java | 47 ++++ ...AutomationPackageFragmentDeserializer.java | 48 +--- ...AbstractAutomationPackageFragmentYaml.java | 21 +- .../model/AutomationPackageFragmentYaml.java | 4 + .../parser/yaml/PatchableYamlArtefact.java | 43 ++++ .../java/step/plans/parser/yaml/YamlPlan.java | 7 +- .../UpgradableYamlPlanDeserializer.java | 7 +- 18 files changed, 558 insertions(+), 88 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello_World_Plan.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterAdd.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModifyAndAdd.yml create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java create mode 100644 step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java 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 index 3413981cbf..4ba860257f 100644 --- 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 @@ -28,9 +28,9 @@ public class AutomationPackagePlanCollection extends InMemoryCollection im private final AutomationPackageYamlFragmentManager fragmentManager; public AutomationPackagePlanCollection(AutomationPackageYamlFragmentManager fragmentManager) { - super(true, "plan"); + super(true, "plans"); this.fragmentManager = fragmentManager; - super.save(fragmentManager.getPlans()); + fragmentManager.getPlans().forEach(super::save); } @Override 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 index b81809bd84..2f1dbb7377 100644 --- 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 @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import step.artefacts.Echo; +import step.artefacts.Sequence; import step.automation.packages.AutomationPackageHookRegistry; import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; @@ -42,10 +43,7 @@ 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.*; import java.util.stream.Collectors; import static org.junit.Assert.*; @@ -60,6 +58,7 @@ public class AutomationPackageCollectionTest { private File destinationDirectory; private Collection planCollection; private final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected"); + private AutomationPackageYamlFragmentManager fragmentManager; public AutomationPackageCollectionTest() throws AutomationPackageReadingException { AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); @@ -77,7 +76,7 @@ public void setUp() throws IOException, AutomationPackageReadingException { destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile(); FileUtils.copyDirectory(sourceDirectory, destinationDirectory); - AutomationPackageYamlFragmentManager fragmentManager = reader.provideAutomationPackageYamlFragmentManager(destinationDirectory); + fragmentManager = reader.provideAutomationPackageYamlFragmentManager(destinationDirectory); AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(properties, fragmentManager); planCollection = collectionFactory.getCollection("plan", Plan.class); } @@ -143,9 +142,105 @@ public void testPlanRemoveExisting() throws IOException { assertFilesEqual(expectedFilesPath.resolve("plan1AfterRemove.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); } + @Test + public void testAddPlanToExistingFragmentWithExistingPlans() throws IOException { + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + Plan plan = new Plan(sequence); + Map attributes = new HashMap<>(); + attributes.put("name", "New Name"); + plan.setAttributes(attributes); + + Properties properties = new Properties(); + properties.setProperty(AutomationPackageYamlFragmentManager.PROPERTY_NEW_PLAN_FRAGMENT_PATH, "plans/plan1.yml"); + fragmentManager.setProperties(properties); + planCollection.save(plan); + + assertFilesEqual(expectedFilesPath.resolve("plan1AfterAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); + } + + @Test + public void testPlanModifyAndAdd() 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); + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + plan = new Plan(sequence); + Map attributes = new HashMap<>(); + attributes.put("name", "New Name"); + plan.setAttributes(attributes); + + Properties properties = new Properties(); + properties.setProperty(AutomationPackageYamlFragmentManager.PROPERTY_NEW_PLAN_FRAGMENT_PATH, "plans/plan1.yml"); + fragmentManager.setProperties(properties); + planCollection.save(plan); + + assertFilesEqual(expectedFilesPath.resolve("plan1AfterModifyAndAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); + } + + @Test + public void testAddPlanToDescriptorWithPresentButEmptyPlanArray() throws IOException { + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + Plan plan = new Plan(sequence); + Map attributes = new HashMap<>(); + attributes.put("name", "New Name"); + plan.setAttributes(attributes); + + planCollection.save(plan); + + assertFilesEqual(expectedFilesPath.resolve("descriptorAfterAdd.yml"), destinationDirectory.toPath().resolve("automation-package.yml")); + } + + + @Test + public void testAddPlanToNewFragment() throws IOException { + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + Plan plan = new Plan(sequence); + Map attributes = new HashMap<>(); + attributes.put("name", "Hello World Plan"); + plan.setAttributes(attributes); + + + Properties properties = new Properties(); + properties.setProperty(AutomationPackageYamlFragmentManager.PROPERTY_NEW_PLAN_FRAGMENT_PATH, "plans/%name%.yml"); + fragmentManager.setProperties(properties); + planCollection.save(plan); + + assertFilesEqual(expectedFilesPath.resolve("Hello_World_Plan.yml"), destinationDirectory.toPath().resolve("plans").resolve("Hello_World_Plan.yml")); + + } + private void assertFilesEqual(Path expected, Path actual) throws IOException { - List expectedLines = Files.readAllLines(expected); - List actualLines = Files.readAllLines(actual); + String expectedLines = Files.readString(expected); + String actualLines = Files.readString(actual); assertEquals(expectedLines, actualLines); } 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 index 13510fa65a..597515a1de 100644 --- 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 @@ -1,5 +1,18 @@ schemaVersion: 1.0.0 name: "My package" +alertingRules: + - name: "Rule1" + description: "My test alerting rule" + eventClass: ExecutionEndedEvent + conditions: + - BindingCondition: + description: "condition 1" + bindingKey: "myKey" + negate: false + predicate: + BindingValueEqualsPredicate: + value: "myValue" +plans: [] fragments: - "keywords.yml" - "plans/*.yml" diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello_World_Plan.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello_World_Plan.yml new file mode 100644 index 0000000000..877fe4ae44 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello_World_Plan.yml @@ -0,0 +1,11 @@ +--- +plans: +- version: "1.2.0" + name: "Hello World Plan" + root: + sequence: + children: + - echo: + text: "Hello World" + agents: null + categories: null \ 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/descriptorAfterAdd.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml new file mode 100644 index 0000000000..9afeca68e0 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml @@ -0,0 +1,31 @@ +schemaVersion: 1.0.0 +name: "My package" +alertingRules: + - name: "Rule1" + description: "My test alerting rule" + eventClass: ExecutionEndedEvent + conditions: + - BindingCondition: + description: "condition 1" + bindingKey: "myKey" + negate: false + predicate: + BindingValueEqualsPredicate: + value: "myValue" +fragments: + - "keywords.yml" + - "plans/*.yml" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.yml" +plans: +- version: "1.2.0" + name: "New Name" + root: + sequence: + children: + - echo: + text: "Hello World" + agents: null + categories: null \ 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/plan1AfterAdd.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterAdd.yml new file mode 100644 index 0000000000..2b847c9cdb --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterAdd.yml @@ -0,0 +1,35 @@ +--- +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" +- version: "1.2.0" + name: "New Name" + root: + sequence: + children: + - echo: + text: "Hello World" + agents: null + categories: null +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/plan1AfterModifyAndAdd.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModifyAndAdd.yml new file mode 100644 index 0000000000..7c8f7d27ea --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModifyAndAdd.yml @@ -0,0 +1,39 @@ +--- +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" +- version: "1.2.0" + name: "New Name" + root: + sequence: + children: + - echo: + text: "Hello World" + agents: null + categories: null +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 index af434784e2..f01060a03f 100644 --- 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 @@ -1,7 +1,6 @@ --- fragments: [] keywords: [] -plans: [] plansPlainText: - name: "Plain text plan" rootType: "Sequence" 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 index 23db1ca1a7..26e6c014f5 100644 --- 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 @@ -23,4 +23,4 @@ plansPlainText: rootType: "Sequence" categories: - "PlainTextPlan" - file: "plans/plan2.plan" \ No newline at end of file + file: "plans/plan2.plan" 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 7a65211340..23eb4dff31 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 @@ -18,6 +18,7 @@ ******************************************************************************/ package step.automation.packages.yaml; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -30,6 +31,7 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; +import step.automation.packages.yaml.deserialization.AdditionalFieldHandler; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; @@ -117,7 +119,7 @@ private T readAutomationPackageYamlFil } T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(yamlDescriptorString, targetClass); - + res.setCurrentYaml(yamlDescriptorString); logAfterRead(packageFileName, res); return res; @@ -170,11 +172,19 @@ private ObjectMapper createYamlObjectMapper() { // Disable native type id to enable conversion to generic Documents yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); + //yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); // Minimize Quotes ObjectMapper yamlMapper = DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); // configure custom deserializers - SimpleModule module = new SimpleModule(); - + SimpleModule module = new SimpleModule();/* { + @Override + public void setupModule(Module.SetupContext context) { + super.setupModule(context); + + context.addDeserializationProblemHandler(new AdditionalFieldHandler(serializationRegistry, yamlMapper)); + } + };*/ + // register deserializers to read yaml plans planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); 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 index 803fbfdfee..1c0d613080 100644 --- 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 @@ -18,38 +18,51 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.core.exc.StreamWriteException; -import com.fasterxml.jackson.databind.DatabindException; -import org.yaml.snakeyaml.Yaml; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.commons.io.FileUtils; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; +import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; +import step.core.accessors.AbstractOrganizableObject; import step.core.plans.Plan; +import step.plans.parser.yaml.PatchableYamlArtefact; import step.plans.parser.yaml.YamlPlan; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.net.MalformedURLException; import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; public class AutomationPackageYamlFragmentManager { + public static final String PROPERTY_NEW_PLAN_FRAGMENT_PATH = "newFragmentPaths.plans"; private final AutomationPackageDescriptorReader descriptorReader; private final Map planToYamlPlan = new ConcurrentHashMap<>(); private final Map planToYamlFragment = new ConcurrentHashMap<>(); + private final Map urlToYamlFragment = new ConcurrentHashMap<>(); + private Properties properties = new Properties(); + private final AutomationPackageFragmentYaml descriptorYaml; public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, AutomationPackageDescriptorReader descriptorReader) { this.descriptorReader = descriptorReader; + this.descriptorYaml = descriptorYaml; initializeMaps(descriptorYaml); } + + public void setProperties(Properties properties) { + this.properties = properties; + } public void initializeMaps(AutomationPackageFragmentYaml fragment) { + urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); for (YamlPlan p: fragment.getPlans()) { Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(p); planToYamlPlan.put(plan, p); @@ -66,34 +79,150 @@ public Iterable getPlans() { } 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); + try { + YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p); + + AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); + if (fragment == null) { + fragment = fragmentForNewPlan(p); + addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); + } else { + YamlPlan yamlPlan = planToYamlPlan.get(p); + modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); + } + planToYamlPlan.put(p, newYamlPlan); + writeFragmentToDisk(fragment); + + return p; + } catch (MalformedURLException e) { + throw new RuntimeException(e); } - - planToYamlPlan.put(p, newYamlPlan); - writeFragment(fragment); - return p; } - private AutomationPackageFragmentYaml newFragmentForPlan(Plan p) { + private void addFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T newEntity) { - throw new UnsupportedOperationException("new Plan creation not yet supported in IDE"); - /* try { - File file = new File(descriptorYaml.getFragmentUrl().toURI()); + String collectionName = newEntity.getCollectionName(); + String yaml = fragment.getCurrentYaml(); - Path file.toPath().getParent().resolveSibling(getRelativePathForNewPlan(p)); + if (!entityList.isEmpty()) { + T lastEntity = entityList.get(entityList.size()-1); + String listItemIndent = yaml.substring(yaml.lastIndexOf("\n", lastEntity.getStartOffset()), lastEntity.getStartOffset()); + String indent = listItemIndent.substring(1).replaceAll("-", " "); + String entityYaml = entityStringWithIndent(indent, newEntity); + String oldString1 = yaml.substring(0, lastEntity.getEndOffset()).trim(); + String newYaml = oldString1 + + listItemIndent + entityYaml + yaml.substring(oldString1.length()); + entityList.add(newEntity); + fragment.setCurrentYaml(newYaml); + } else { + entityList.add(newEntity); + String listYaml = collectionName + ":\n" + + entityStringWithIndent("", entityList); - } catch (URISyntaxException e) { + if (yaml == null) { + yaml = "---\n" + listYaml; + } else { + yaml = removeEmptyCollection(collectionName, yaml).trim() + + "\n" + listYaml; + } + + fragment.setCurrentYaml(yaml); + } + } catch (JsonProcessingException e) { throw new RuntimeException(e); - }*/ + } + updateFragmentObjectOffsets(fragment); + } + + private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T oldEntity, T newEntity){ + try { + entityList.replaceAll(plan -> plan == oldEntity ? newEntity : plan); + + String oldString = fragment.getCurrentYaml(); + + int indent = oldEntity.getIndent(); + String indentString = " ".repeat(indent); + + + String newArtefactString = entityStringWithIndent(indentString, newEntity); + + int s = oldEntity.getStartOffset(); + int e = oldEntity.getEndOffset(); + int i = oldString.lastIndexOf("\n", e); + + if (i > 0 && oldString.substring(i, e).trim().isEmpty()) { + e = i; + } + + String newString = oldString.substring(0, s) + + newArtefactString + oldString.substring(e); + fragment.setCurrentYaml(newString); + } catch (IOException e) { + throw new RuntimeException(e); + } + + updateFragmentObjectOffsets(fragment); + } + + private String entityStringWithIndent(String indentString, Object entity) throws JsonProcessingException { + return descriptorReader + .getYamlObjectMapper() + .writeValueAsString(entity) + .replaceAll("---\n", "") + .trim() + .replaceAll("\n", "\n" + indentString); + } + + private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T entity) { + String oldString = fragment.getCurrentYaml(); + + int s = entity.getStartOffset(); + s = oldString.lastIndexOf("\n", s); + + int e = entity.getEndOffset(); + int i = oldString.lastIndexOf("\n", e); + + if (i > 0 && oldString.substring(i, e).trim().isEmpty()) { + e = i; + } + + String newString = oldString.substring(0, s) + oldString.substring(e); + + if (entityList.isEmpty()) { + String collectionName = entity.getCollectionName(); + newString = removeEmptyCollection(collectionName, newString); + } + fragment.setCurrentYaml(newString); + updateFragmentObjectOffsets(fragment); + } + + private String removeEmptyCollection(String collectionName, String yaml) { + return yaml.replaceAll("\n*" + collectionName + ":.*\n*", "\n"); + } + + private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) throws MalformedURLException { + + String planFragmentPath = properties.getProperty(PROPERTY_NEW_PLAN_FRAGMENT_PATH, descriptorYaml.getFragmentUrl().getPath()); + planFragmentPath = planFragmentPath.replaceAll("\\%name\\%", sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME))); + + Path path = new File(planFragmentPath).toPath(); + if (!path.isAbsolute()) { + Path apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) + .getParent(); + path = apRoot.resolve(path); + } + + URL url = path.toUri().toURL(); + + if (urlToYamlFragment.containsKey(url)) return urlToYamlFragment.get(url); + AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); + fragment.setFragmentUrl(url); + return fragment; + } + + public String sanitizeFilename(String inputName) { + return inputName.replaceAll("[^a-zA-Z0-9-_\\.]", "_"); } public void removePlan(Plan p) { @@ -105,14 +234,33 @@ public void removePlan(Plan p) { planToYamlPlan.remove(p); planToYamlFragment.remove(p); - writeFragment(fragment); + removeFragmentEntity(fragment, fragment.getPlans(), yamlPlan); + writeFragmentToDisk(fragment); + } + + private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) { + try { + AutomationPackageFragmentYaml newFragment = descriptorReader.getYamlObjectMapper().readValue(fragment.getCurrentYaml(), fragment.getClass()); + updateFragmentObjectOffsets(newFragment.getPlans(), fragment.getPlans()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private void updateFragmentObjectOffsets(List newOffsetEntities, List entities) { + Iterator newIt = newOffsetEntities.iterator(); + Iterator it = entities.iterator(); + + while (newIt.hasNext() && it.hasNext()) { + it.next().setPatchingBounds(newIt.next()); + } } - private void writeFragment(AutomationPackageFragmentYaml fragment) { + private void writeFragmentToDisk(AutomationPackageFragmentYaml fragment) { try { File file = new File(fragment.getFragmentUrl().toURI()); - descriptorReader.getYamlObjectMapper().writeValue(file, fragment); - } catch (URISyntaxException | IOException e) { + FileUtils.writeStringToFile(file, fragment.getCurrentYaml(), StandardCharsets.UTF_8); + } catch (IOException | URISyntaxException e) { throw new RuntimeException(e); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java new file mode 100644 index 0000000000..efb6bb0d18 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java @@ -0,0 +1,47 @@ +package step.automation.packages.yaml.deserialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.node.ObjectNode; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.core.yaml.SerializationUtils; + +import java.io.IOException; +import java.util.*; + +public class AdditionalFieldHandler extends DeserializationProblemHandler { + + private final ObjectMapper objectMapper; + private final Map> additionalFields = new HashMap<>(); + private final AutomationPackageSerializationRegistry registry; + + public AdditionalFieldHandler(AutomationPackageSerializationRegistry registry, ObjectMapper mapper) { + this.registry = registry; + objectMapper = mapper; + } + + @Override + public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer deserializer, Object beanOrClass, String propertyName) throws IOException { + + JsonNode node = p.getCodec().readTree(p); + + // acquire reader for the right type + Class targetClass = registry.resolveClassForYamlField(propertyName); + if (targetClass == null) return false; + + List list = objectMapper.readerForListOf(targetClass).readValue(node); + additionalFields.put(propertyName, list); + + + return false; + } + + public Map> getAdditionalFields() { + return additionalFields; + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java index 3e9441978d..f6e00d0e18 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java @@ -20,9 +20,9 @@ import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.util.LinkedNode; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; import step.automation.packages.yaml.model.AbstractAutomationPackageFragmentYaml; @@ -30,7 +30,6 @@ import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.yaml.deserializers.StepYamlDeserializer; import step.core.yaml.deserializers.StepYamlDeserializerAddOn; -import step.core.yaml.SerializationUtils; import java.io.IOException; import java.util.*; @@ -48,42 +47,17 @@ public YamlAutomationPackageFragmentDeserializer(ObjectMapper yamlObjectMapper) @Override public AbstractAutomationPackageFragmentYaml deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { JsonDeserializer defaultDeserializerForClass = getDefaultDeserializerForClass(p, ctxt, getObjectClass()); - ObjectCodec oc = p.getCodec(); - JsonNode node = oc.readTree(p); - - ObjectNode nonBasicFields = node.deepCopy(); - Class clazz = getObjectClass(); - List basicFields = SerializationUtils.getJsonFieldNames(yamlObjectMapper, clazz); - nonBasicFields.remove(basicFields); - - try (JsonParser treeParser = oc.treeAsTokens(node)) { - ctxt.getConfig().initialize(treeParser); - - if (treeParser.getCurrentToken() == null) { - treeParser.nextToken(); + AbstractAutomationPackageFragmentYaml res = (AbstractAutomationPackageFragmentYaml) defaultDeserializerForClass.deserialize(p, ctxt); + + LinkedNode handlers = ctxt.getConfig().getProblemHandlers(); + if (handlers != null) { + AdditionalFieldHandler handler = (AdditionalFieldHandler) ctxt.getConfig().getProblemHandlers().value(); + if (handler != null) { + res.setAdditionalFields(handler.getAdditionalFields()); } - AbstractAutomationPackageFragmentYaml res = (AbstractAutomationPackageFragmentYaml) defaultDeserializerForClass.deserialize(treeParser, ctxt); - - if (registry != null) { - Map> nonBasicFieldsMap = new HashMap<>(); - Iterator> fields = nonBasicFields.fields(); - while (fields.hasNext()) { - Map.Entry next = fields.next(); - List list = new ArrayList<>(); - if (next.getValue() != null) { - // acquire reader for the right type - Class targetClass = registry.resolveClassForYamlField(next.getKey()); - if (targetClass != null) { - list = yamlObjectMapper.readerForListOf(targetClass).readValue(next.getValue()); - } - } - nonBasicFieldsMap.put(next.getKey(), list); - } - res.setAdditionalFields(nonBasicFieldsMap); - } - return res; } - + + return res; } protected Class getObjectClass() { 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 82b486e780..4530558484 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 @@ -19,6 +19,7 @@ package step.automation.packages.yaml.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonMerge; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; import step.automation.packages.model.YamlAutomationPackageKeyword; @@ -26,10 +27,7 @@ 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; +import java.util.*; public abstract class AbstractAutomationPackageFragmentYaml implements AutomationPackageFragmentYaml { private List fragments = new ArrayList<>(); @@ -38,10 +36,13 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio private List plansPlainText = new ArrayList<>(); @JsonIgnore - private Map> additionalFields; + private Map> additionalFields = new HashMap<>(); @JsonIgnore private URL url; + + @JsonIgnore + private String currentYaml; @JsonIgnore private List children = new LinkedList<>(); @@ -108,6 +109,16 @@ public URL getFragmentUrl() { return url; } + @JsonIgnore + public void setCurrentYaml(String yaml) { + this.currentYaml = yaml; + } + + @JsonIgnore + public String getCurrentYaml() { + return currentYaml; + } + @JsonIgnore public List getChildren() { return children; 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 e9ad660af9..38c8662499 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 @@ -45,6 +45,10 @@ default List getAdditionalField(String k) { URL getFragmentUrl(); void setFragmentUrl(URL url); + + String getCurrentYaml(); + + void setCurrentYaml(String yaml); List getChildren(); diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java new file mode 100644 index 0000000000..7f48b0f59f --- /dev/null +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java @@ -0,0 +1,43 @@ +package step.plans.parser.yaml; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonLocation; + +public abstract class PatchableYamlArtefact { + + private int startOffset = -1; + private int startColumn = -1; + private int endOffset = -1; + + @JsonIgnore + public void setPatchingBounds(JsonLocation startLocation, JsonLocation endLocation) { + startOffset = (int) startLocation.getCharOffset(); + endOffset = (int) endLocation.getCharOffset(); + startColumn = startLocation.getColumnNr() -1; + } + + @JsonIgnore + public int getStartOffset(){ + return startOffset; + } + + @JsonIgnore + public int getIndent() { + return startColumn; + } + + @JsonIgnore + public int getEndOffset() { + return endOffset; + } + + + public void setPatchingBounds(PatchableYamlArtefact newBoundedArtefact) { + startOffset = newBoundedArtefact.startOffset; + startColumn = newBoundedArtefact.startColumn; + endOffset = newBoundedArtefact.endOffset; + } + + @JsonIgnore + abstract public String getCollectionName(); +} diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java index 2e85acc68c..915d472804 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java @@ -27,7 +27,7 @@ import java.util.List; -public class YamlPlan { +public class YamlPlan extends PatchableYamlArtefact { public static final String PLANS_ENTITY_NAME = "plans"; @@ -84,4 +84,9 @@ public List getCategories() { public void setCategories(List categories) { this.categories = categories; } + + @Override + public String getCollectionName() { + return PLANS_ENTITY_NAME; + } } diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java index 98b9fe3d53..8007c4131f 100644 --- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java +++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java @@ -18,6 +18,7 @@ ******************************************************************************/ package step.plans.parser.yaml.deserializers; +import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -60,7 +61,9 @@ public UpgradableYamlPlanDeserializer(Version currentVersion, String jsonSchema, @Override public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonLocation startLocation = p.currentLocation(); JsonNode planJsonNode = p.readValueAsTree(); + JsonLocation endLocation = p.currentLocation(); if (currentVersion != null) { Document yamlPlanDocument = p.getCodec().treeToValue(planJsonNode, Document.class); @@ -115,7 +118,9 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO } } - return yamlMapper.treeToValue(planJsonNode, YamlPlan.class); + YamlPlan yamlPlan = yamlMapper.treeToValue(planJsonNode, YamlPlan.class); + yamlPlan.setPatchingBounds(startLocation, endLocation); + return yamlPlan; } } From 99c2105d42e3e952aaa59e27a597db481aad6bb8 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 13 Mar 2026 15:24:41 +0100 Subject: [PATCH 05/15] SED-4539 refactor additional field handling --- .../AutomationPackageDescriptorReader.java | 19 ++--- .../AdditionalFieldHandler.java | 47 ------------ ...tomationPackageDescriptorDeserializer.java | 37 ---------- ...AutomationPackageFragmentDeserializer.java | 71 ------------------- ...AbstractAutomationPackageFragmentYaml.java | 40 ++++++++--- .../AutomationPackageDescriptorYamlImpl.java | 15 ++++ .../model/AutomationPackageFragmentYaml.java | 4 ++ .../AutomationPackageFragmentYamlImpl.java | 12 ++++ 8 files changed, 69 insertions(+), 176 deletions(-) delete mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java delete mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java delete mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java 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 23eb4dff31..20d0b80ae6 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 @@ -18,8 +18,7 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; @@ -31,7 +30,6 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; -import step.automation.packages.yaml.deserialization.AdditionalFieldHandler; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; @@ -174,16 +172,13 @@ private ObjectMapper createYamlObjectMapper() { yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); //yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); // Minimize Quotes ObjectMapper yamlMapper = DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); - + + yamlMapper.setInjectableValues(new InjectableValues.Std() + .addValue(ObjectMapper.class, yamlMapper) + .addValue(AutomationPackageSerializationRegistry.class, serializationRegistry) + ); // configure custom deserializers - SimpleModule module = new SimpleModule();/* { - @Override - public void setupModule(Module.SetupContext context) { - super.setupModule(context); - - context.addDeserializationProblemHandler(new AdditionalFieldHandler(serializationRegistry, yamlMapper)); - } - };*/ + SimpleModule module = new SimpleModule(); // register deserializers to read yaml plans planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java deleted file mode 100644 index efb6bb0d18..0000000000 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AdditionalFieldHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -package step.automation.packages.yaml.deserialization; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; -import com.fasterxml.jackson.databind.node.ObjectNode; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.core.yaml.SerializationUtils; - -import java.io.IOException; -import java.util.*; - -public class AdditionalFieldHandler extends DeserializationProblemHandler { - - private final ObjectMapper objectMapper; - private final Map> additionalFields = new HashMap<>(); - private final AutomationPackageSerializationRegistry registry; - - public AdditionalFieldHandler(AutomationPackageSerializationRegistry registry, ObjectMapper mapper) { - this.registry = registry; - objectMapper = mapper; - } - - @Override - public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer deserializer, Object beanOrClass, String propertyName) throws IOException { - - JsonNode node = p.getCodec().readTree(p); - - // acquire reader for the right type - Class targetClass = registry.resolveClassForYamlField(propertyName); - if (targetClass == null) return false; - - List list = objectMapper.readerForListOf(targetClass).readValue(node); - additionalFields.put(propertyName, list); - - - return false; - } - - public Map> getAdditionalFields() { - return additionalFields; - } -} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java deleted file mode 100644 index e81e60510d..0000000000 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, 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.deserialization; - -import com.fasterxml.jackson.databind.ObjectMapper; -import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; -import step.core.yaml.deserializers.StepYamlDeserializerAddOn; - -@StepYamlDeserializerAddOn(targetClasses = {AutomationPackageDescriptorYamlImpl.class}) -public class YamlAutomationPackageDescriptorDeserializer extends YamlAutomationPackageFragmentDeserializer { - - public YamlAutomationPackageDescriptorDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); - } - - @Override - protected Class getObjectClass() { - return AutomationPackageDescriptorYamlImpl.class; - } - -} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java deleted file mode 100644 index f6e00d0e18..0000000000 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, 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.deserialization; - -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; -import com.fasterxml.jackson.databind.util.LinkedNode; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; -import step.automation.packages.yaml.model.AbstractAutomationPackageFragmentYaml; -import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; -import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; -import step.core.yaml.deserializers.StepYamlDeserializer; -import step.core.yaml.deserializers.StepYamlDeserializerAddOn; - -import java.io.IOException; -import java.util.*; - -@StepYamlDeserializerAddOn(targetClasses = {AutomationPackageFragmentYamlImpl.class}) -public class YamlAutomationPackageFragmentDeserializer extends StepYamlDeserializer - implements AutomationPackageSerializationRegistryAware { - - protected AutomationPackageSerializationRegistry registry; - - public YamlAutomationPackageFragmentDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); - } - - @Override - public AbstractAutomationPackageFragmentYaml deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { - JsonDeserializer defaultDeserializerForClass = getDefaultDeserializerForClass(p, ctxt, getObjectClass()); - AbstractAutomationPackageFragmentYaml res = (AbstractAutomationPackageFragmentYaml) defaultDeserializerForClass.deserialize(p, ctxt); - - LinkedNode handlers = ctxt.getConfig().getProblemHandlers(); - if (handlers != null) { - AdditionalFieldHandler handler = (AdditionalFieldHandler) ctxt.getConfig().getProblemHandlers().value(); - if (handler != null) { - res.setAdditionalFields(handler.getAdditionalFields()); - } - } - - return res; - } - - protected Class getObjectClass() { - return AutomationPackageFragmentYamlImpl.class; - } - - @Override - public void setSerializationRegistry(AutomationPackageSerializationRegistry registry) { - this.registry = registry; - } -} 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 4530558484..b24ccc251d 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 @@ -18,25 +18,38 @@ ******************************************************************************/ package step.automation.packages.yaml.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonMerge; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; +import java.io.IOException; import java.net.URL; import java.util.*; public abstract class AbstractAutomationPackageFragmentYaml implements AutomationPackageFragmentYaml { + private final ObjectMapper mapper; + private final AutomationPackageSerializationRegistry serializationRegistry; private List fragments = new ArrayList<>(); private List keywords = new ArrayList<>(); private List plans = new ArrayList<>(); private List plansPlainText = new ArrayList<>(); - @JsonIgnore - private Map> additionalFields = new HashMap<>(); + private final Map> additionalFields = new HashMap<>(); + + @JsonCreator + public AbstractAutomationPackageFragmentYaml(@JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry) { + this.mapper = mapper; + this.serializationRegistry = serializationRegistry; + } + + public AbstractAutomationPackageFragmentYaml() { + this.mapper = null; + this.serializationRegistry = null; + }; @JsonIgnore private URL url; @@ -80,13 +93,22 @@ public void setFragments(List fragments) { this.fragments = fragments; } - @Override + @JsonAnyGetter public Map> getAdditionalFields() { return additionalFields; } - public void setAdditionalFields(Map> additionalFields) { - this.additionalFields = additionalFields; + @JsonAnySetter + @Override + public void setAdditionalFields(String key, JsonNode node) throws IOException { + if (mapper == null || serializationRegistry == null) return; + + // acquire reader for the right type + Class targetClass = serializationRegistry.resolveClassForYamlField(key); + if (targetClass == null) return; + + List list = mapper.readerForListOf(targetClass).readValue(node); + additionalFields.put(key, list); } @Override diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageDescriptorYamlImpl.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageDescriptorYamlImpl.java index 2dbaf31f1c..a39b1f51db 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageDescriptorYamlImpl.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageDescriptorYamlImpl.java @@ -18,6 +18,12 @@ ******************************************************************************/ package step.automation.packages.yaml.model; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; +import com.fasterxml.jackson.databind.ObjectMapper; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; + import java.util.HashMap; import java.util.Map; @@ -29,6 +35,15 @@ public class AutomationPackageDescriptorYamlImpl extends AbstractAutomationPacka private String name; + @JsonCreator + public AutomationPackageDescriptorYamlImpl(@JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry) { + super(mapper, serializationRegistry); + } + + public AutomationPackageDescriptorYamlImpl() { + super(); + } + @Override public String getName() { return name; 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 38c8662499..102dc34950 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 @@ -18,10 +18,12 @@ ******************************************************************************/ package step.automation.packages.yaml.model; +import com.fasterxml.jackson.databind.JsonNode; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; +import java.io.IOException; import java.net.URL; import java.util.List; import java.util.Map; @@ -41,6 +43,8 @@ public interface AutomationPackageFragmentYaml { default List getAdditionalField(String k) { return (List) getAdditionalFields().get(k); } + + void setAdditionalFields(String key, JsonNode value) throws IOException; URL getFragmentUrl(); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYamlImpl.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYamlImpl.java index f0fdcc15e0..3ec0ac228f 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYamlImpl.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/model/AutomationPackageFragmentYamlImpl.java @@ -18,6 +18,18 @@ ******************************************************************************/ package step.automation.packages.yaml.model; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; +import com.fasterxml.jackson.databind.ObjectMapper; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; + public class AutomationPackageFragmentYamlImpl extends AbstractAutomationPackageFragmentYaml { + @JsonCreator + public AutomationPackageFragmentYamlImpl(@JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry) { + super(mapper, serializationRegistry); + } + + public AutomationPackageFragmentYamlImpl() { super();} } From 364d3b491a9e24ec9e77f3828f433c95ab8745d3 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 13 Mar 2026 19:41:07 +0100 Subject: [PATCH 06/15] SED-4539 simplify Fragment structure --- .../packages/AutomationPackageReader.java | 102 +++++++++--------- .../AutomationPackageDescriptorReader.java | 53 +++------ .../AutomationPackageYamlFragmentManager.java | 12 +-- ...AbstractAutomationPackageFragmentYaml.java | 21 ---- .../model/AutomationPackageFragmentYaml.java | 6 -- 5 files changed, 71 insertions(+), 123 deletions(-) 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 85d3f89a9f..c74a8710fc 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 @@ -86,51 +86,49 @@ public AutomationPackageArchive createAutomationPackageArchive(File automationPa abstract public List getSupportedFileTypes(); - /** - * @param isClasspathBased true if the automation package is located in current classloader (i.e. all annotated keywords - * can be read as {@link step.engine.plugins.LocalFunctionPlugin.LocalFunction}, but not as {@link GeneralScriptFunction} - */ - public AutomationPackageContent readAutomationPackage(T automationPackageArchive, String apVersion, boolean isClasspathBased) throws AutomationPackageReadingException { - return this.readAutomationPackage(automationPackageArchive, apVersion, isClasspathBased, true); + public AutomationPackageContent readAutomationPackage(T automationPackageArchive, String apVersion, boolean isClassPathBased) throws AutomationPackageReadingException { + return this.readAutomationPackage(automationPackageArchive, apVersion, isClassPathBased, true); } /** - * @param isClasspathBased true if the automation package is located in current classloader (i.e. all annotated keywords - * 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 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; + 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.getAutomationPackageName()); + 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); } } - - protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescriptorYaml descriptor, T archive, String apVersion, - boolean isClasspathBased, boolean scanAnnotations) throws AutomationPackageReadingException { + private AutomationPackageContent buildAutomationPackage(AutomationPackageDescriptorYaml descriptor, T archive, String apVersion, + boolean isClassPathBased, boolean scanAnnotations) throws AutomationPackageReadingException { AutomationPackageContent res = newContentInstance(); String baseName = resolveName(descriptor, archive); res.setBaseName(baseName); res.setName(resolveUniqueName(baseName, apVersion)); if (scanAnnotations) { - fillAutomationPackageWithAnnotatedKeywordsAndPlans(archive, isClasspathBased, res); + fillAutomationPackageWithAnnotatedKeywordsAndPlans(archive, isClassPathBased, res); } // apply imported fragments recursively if (descriptor != null) { - readAutomationPackageYamlFragmentTree(archive, descriptor); - fillAutomationPackageWithImportedFragments(res, descriptor, archive); + Map fragmentMap = new HashMap<>(); + fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); } return res; } - private String resolveName(AutomationPackageDescriptorYaml descriptor, T archive) throws AutomationPackageReadingException { String finalName; if (descriptor != null) { @@ -155,8 +153,8 @@ private String validatePackageName(String name) throws AutomationPackageReadingE // Check for characters that could break Groovy expressions if (name.contains("'") || name.contains("\\")) { throw new AutomationPackageReadingException( - "Package name contains unsafe characters: " + name + - ". Simple quote and backslash characters are not allowed." + "Package name contains unsafe characters: " + name + + ". Simple quote and backslash characters are not allowed." ); } return name; @@ -171,51 +169,51 @@ private String resolveUniqueName(String baseName, String apVersion) { return finalName; } - protected AutomationPackageContent newContentInstance(){ + protected AutomationPackageContent newContentInstance() { return new AutomationPackageContent(); } - abstract protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(T archive, boolean isClasspathBased, AutomationPackageContent res) throws AutomationPackageReadingException; + abstract protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(T archive, boolean isClassPathBased, AutomationPackageContent res) throws AutomationPackageReadingException; 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()); + URL descriptorURL = archive.getDescriptorYamlUrl(); + try (InputStream inputStream = descriptorURL.openStream()){ + AutomationPackageDescriptorYaml descriptor = reader.readAutomationPackageDescriptor(inputStream, archive.getOriginalFileName()); + descriptor.setFragmentUrl(descriptorURL); + AutomationPackageContent res = newContentInstance(); + Map fragmentMap = new HashMap<>(); + fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); + return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); + } catch (IOException e) { + throw new RuntimeException(e); + } } + + private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive, Map fragmentYamlMap) throws AutomationPackageReadingException { + fillContentSections(targetPackage, fragment, archive); - private void readAutomationPackageYamlFragmentTree(AutomationPackageArchive archive, AutomationPackageFragmentYaml parent) throws AutomationPackageReadingException { - - if (!parent.getFragments().isEmpty()) { - for (String importedFragmentReference : parent.getFragments()) { + if (!fragment.getFragments().isEmpty()) { + for (String importedFragmentReference : fragment.getFragments()) { List resources = archive.getResourcesByPattern(importedFragmentReference); for (URL resource : resources) { - AutomationPackageFragmentYaml fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(resource, archive.getOriginalFileName()); - fragment.setParent(parent); - parent.getChildren().add(fragment); - readAutomationPackageYamlFragmentTree(archive, fragment); + try (InputStream fragmentYamlStream = resource.openStream()) { + fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, importedFragmentReference, archive.getAutomationPackageName()); + fragmentYamlMap.put(resource, fragment); + fragment.setFragmentUrl(resource); + fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive, fragmentYamlMap); + } catch (IOException e) { + throw new AutomationPackageReadingException("Unable to read fragment in automation package: " + importedFragmentReference, e); + } } } } } - 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 -> { - Plan plan = getOrCreateDescriptorReader().getPlanReader().yamlPlanToPlan(p); - plan.getAttributes().put("fragmentUrl", fragment.getFragmentUrl().toString()); - plan.getAttributes().put("nameInYaml", p.getName()); - return plan; - }).collect(Collectors.toList())); + targetPackage.getPlans().addAll(fragment.getPlans().stream().map(p -> getOrCreateDescriptorReader().getPlanReader().yamlPlanToPlan(p)).collect(Collectors.toList())); readPlainTextPlans(targetPackage, fragment, archive); 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 20d0b80ae6..0129d092ff 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 @@ -18,7 +18,8 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.InjectableValues; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; @@ -42,7 +43,6 @@ 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; @@ -71,35 +71,25 @@ public AutomationPackageDescriptorReader(String jsonSchemaPath, AutomationPackag } } - public AutomationPackageDescriptorYaml readAutomationPackageDescriptor(URL resource, String packageFileName) throws AutomationPackageReadingException { + public AutomationPackageDescriptorYaml readAutomationPackageDescriptor(InputStream yamlDescriptor, String packageName) throws AutomationPackageReadingException { log.info("Reading automation package descriptor..."); - 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); + return readAutomationPackageYamlFile(yamlDescriptor, getDescriptorClass(), packageName); } protected Class getDescriptorClass() { return AutomationPackageDescriptorYamlImpl.class; } - 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 { + public AutomationPackageFragmentYaml readAutomationPackageFragment(InputStream yamlFragment, String fragmentName, String packageName) throws AutomationPackageReadingException { log.info("Reading automation package descriptor fragment ({})...", fragmentName); - return readAutomationPackageYamlFile(yaml, getFragmentClass(), packageFileName); + return readAutomationPackageYamlFile(yamlFragment, getFragmentClass(), packageName); } protected Class getFragmentClass() { return AutomationPackageFragmentYamlImpl.class; } - private T readAutomationPackageYamlFile(InputStream yaml, Class targetClass, String packageFileName) throws AutomationPackageReadingException { + protected T readAutomationPackageYamlFile(InputStream yaml, Class targetClass, String packageName) throws AutomationPackageReadingException { try { String yamlDescriptorString = new String(yaml.readAllBytes(), StandardCharsets.UTF_8); String version = null; @@ -118,39 +108,28 @@ private T readAutomationPackageYamlFil T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(yamlDescriptorString, targetClass); res.setCurrentYaml(yamlDescriptorString); - logAfterRead(packageFileName, res); - + logAfterRead(packageName, 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); - } - } - - protected void logAfterRead(String packageFileName, T res) { + protected void logAfterRead(String packageName, T res) { if (!res.getKeywords().isEmpty()) { - log.info("{} keyword(s) found in automation package {}", res.getKeywords().size(), StringUtils.defaultString(packageFileName)); + log.info("{} keyword(s) found in automation package {}", res.getKeywords().size(), StringUtils.defaultString(packageName)); } if (!res.getPlans().isEmpty()) { - log.info("{} plan(s) found in automation package {}", res.getPlans().size(), StringUtils.defaultString(packageFileName)); + log.info("{} plan(s) found in automation package {}", res.getPlans().size(), StringUtils.defaultString(packageName)); } if (!res.getPlansPlainText().isEmpty()) { - log.info("{} plain text plan(s) found in automation package {}", res.getPlans().size(), StringUtils.defaultString(packageFileName)); + log.info("{} plain text plan(s) found in automation package {}", res.getPlans().size(), StringUtils.defaultString(packageName)); } for (Map.Entry> additionalEntry : res.getAdditionalFields().entrySet()) { - log.info("{} {} found in automation package {}", additionalEntry.getValue().size(), additionalEntry.getKey(), StringUtils.defaultString(packageFileName)); + log.info("{} {} found in automation package {}", additionalEntry.getValue().size(), additionalEntry.getKey(), StringUtils.defaultString(packageName)); } if (!res.getFragments().isEmpty()) { - log.info("{} imported fragment(s) found in automation package {}", res.getFragments().size(), StringUtils.defaultString(packageFileName)); + log.info("{} imported fragment(s) found in automation package {}", res.getFragments().size(), StringUtils.defaultString(packageName)); } } @@ -165,12 +144,11 @@ protected String readJsonSchema(String jsonSchemaPath) { } } - private ObjectMapper createYamlObjectMapper() { + public ObjectMapper createYamlObjectMapper() { YAMLFactory yamlFactory = new YAMLFactory(); // Disable native type id to enable conversion to generic Documents yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); - //yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); // Minimize Quotes ObjectMapper yamlMapper = DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); yamlMapper.setInjectableValues(new InjectableValues.Std() @@ -190,7 +168,6 @@ private ObjectMapper createYamlObjectMapper() { } })); - yamlMapper.registerModule(module); return yamlMapper; 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 index 1c0d613080..8c366368b4 100644 --- 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 @@ -45,16 +45,20 @@ public class AutomationPackageYamlFragmentManager { private final Map planToYamlPlan = new ConcurrentHashMap<>(); private final Map planToYamlFragment = new ConcurrentHashMap<>(); - private final Map urlToYamlFragment = new ConcurrentHashMap<>(); + private final Map urlToYamlFragment; private Properties properties = new Properties(); private final AutomationPackageFragmentYaml descriptorYaml; - public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, AutomationPackageDescriptorReader descriptorReader) { + public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, Map fragmentMap, AutomationPackageDescriptorReader descriptorReader) { this.descriptorReader = descriptorReader; this.descriptorYaml = descriptorYaml; + + urlToYamlFragment = fragmentMap; initializeMaps(descriptorYaml); + + urlToYamlFragment.values().forEach(this::initializeMaps); } public void setProperties(Properties properties) { @@ -68,10 +72,6 @@ public void initializeMaps(AutomationPackageFragmentYaml fragment) { planToYamlPlan.put(plan, p); planToYamlFragment.put(plan, fragment); }; - - for (AutomationPackageFragmentYaml child : fragment.getChildren()) { - initializeMaps(child); - } } public Iterable getPlans() { 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 b24ccc251d..dd45701bf9 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 @@ -57,12 +57,6 @@ public AbstractAutomationPackageFragmentYaml() { @JsonIgnore private String currentYaml; - @JsonIgnore - private List children = new LinkedList<>(); - - @JsonIgnore - private AutomationPackageFragmentYaml parent; - @Override public List getKeywords() { return keywords; @@ -140,19 +134,4 @@ public void setCurrentYaml(String yaml) { public String getCurrentYaml() { return currentYaml; } - - @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 102dc34950..14db5bf624 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 @@ -53,10 +53,4 @@ default List getAdditionalField(String k) { String getCurrentYaml(); void setCurrentYaml(String yaml); - - List getChildren(); - - AutomationPackageFragmentYaml getParent(); - - void setParent(AutomationPackageFragmentYaml parent); } From b9ace31ca7c20d327ac54d45683f693bcd15b044 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Tue, 17 Mar 2026 10:02:55 +0100 Subject: [PATCH 07/15] SED-4539 cleaner string manipulations --- .../AutomationPackageCollectionTest.java | 46 +++++++++++- .../AutomationPackageDescriptorReader.java | 29 +++++++- .../AutomationPackageYamlFragmentManager.java | 73 +++++++------------ .../PatchableYamlArtefactDeserializer.java | 70 ++++++++++++++++++ .../PatchingParserDelegate.java | 41 +++++++++++ .../parser/yaml/PatchableYamlArtefact.java | 24 +++++- .../plans/parser/yaml/YamlPlanReader.java | 2 +- .../UpgradableYamlPlanDeserializer.java | 3 - 8 files changed, 227 insertions(+), 61 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java 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 index 2f1dbb7377..88f0840b62 100644 --- 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 @@ -149,12 +149,12 @@ public void testAddPlanToExistingFragmentWithExistingPlans() throws IOException Echo echo = new Echo(); echo.setText(new DynamicValue<>("Hello World")); sequence.addChild(echo); - + Plan plan = new Plan(sequence); Map attributes = new HashMap<>(); attributes.put("name", "New Name"); plan.setAttributes(attributes); - + Properties properties = new Properties(); properties.setProperty(AutomationPackageYamlFragmentManager.PROPERTY_NEW_PLAN_FRAGMENT_PATH, "plans/plan1.yml"); fragmentManager.setProperties(properties); @@ -177,7 +177,40 @@ public void testPlanModifyAndAdd() throws IOException { text.setExpression("new Date().toString();"); planCollection.save(plan); - + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + plan = new Plan(sequence); + Map attributes = new HashMap<>(); + attributes.put("name", "New Name"); + plan.setAttributes(attributes); + + Properties properties = new Properties(); + properties.setProperty(AutomationPackageYamlFragmentManager.PROPERTY_NEW_PLAN_FRAGMENT_PATH, "plans/plan1.yml"); + fragmentManager.setProperties(properties); + planCollection.save(plan); + + assertFilesEqual(expectedFilesPath.resolve("plan1AfterModifyAndAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); + } + + @Test + public void testPlanModifyAndAddAndRemove() 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); + Sequence sequence = new Sequence(); Echo echo = new Echo(); echo.setText(new DynamicValue<>("Hello World")); @@ -194,6 +227,11 @@ public void testPlanModifyAndAdd() throws IOException { planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("plan1AfterModifyAndAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); + + planCollection.remove(Filters.equals("attributes.name", "New Name")); + + + assertFilesEqual(expectedFilesPath.resolve("plan1AfterModification.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); } @Test @@ -235,7 +273,7 @@ public void testAddPlanToNewFragment() throws IOException { planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("Hello_World_Plan.yml"), destinationDirectory.toPath().resolve("plans").resolve("Hello_World_Plan.yml")); - + } private void assertFilesEqual(Path expected, Path actual) throws IOException { 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 0fb72bc198..2be2fe3ff4 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 @@ -18,8 +18,10 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.databind.InjectableValues; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; @@ -31,12 +33,15 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; +import step.automation.packages.yaml.deserialization.PatchableYamlArtefactDeserializer; +import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.accessors.DefaultJacksonMapperProvider; import step.core.yaml.deserializers.StepYamlDeserializersScanner; +import step.plans.parser.yaml.PatchableYamlArtefact; import step.plans.parser.yaml.YamlPlanReader; import step.plans.parser.yaml.model.YamlPlanVersions; import step.plans.parser.yaml.schema.YamlPlanValidationException; @@ -106,7 +111,8 @@ protected T readAutomationPackageYamlF } } - T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(yamlDescriptorString, targetClass); + JsonParser parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString)); + T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(parser, targetClass); res.setCurrentYaml(yamlDescriptorString); logAfterRead(packageName, res); return res; @@ -156,7 +162,22 @@ public ObjectMapper createYamlObjectMapper() { .addValue(AutomationPackageSerializationRegistry.class, serializationRegistry) ); // configure custom deserializers - SimpleModule module = new SimpleModule(); + SimpleModule module = new SimpleModule() { + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + + context.addBeanDeserializerModifier(new BeanDeserializerModifier() { + @Override + public JsonDeserializer modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { + if (PatchableYamlArtefact.class.isAssignableFrom(beanDesc.getBeanClass())) { + return new PatchableYamlArtefactDeserializer<>(deserializer); + } + return super.modifyDeserializer(config, beanDesc, deserializer); + } + }); + } + }; // register deserializers to read yaml plans planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); 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 index 8c366368b4..4e24471faf 100644 --- 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 @@ -18,8 +18,11 @@ ******************************************************************************/ package step.automation.packages.yaml; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.FileUtils; +import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; @@ -53,14 +56,14 @@ public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml desc this.descriptorReader = descriptorReader; this.descriptorYaml = descriptorYaml; - + urlToYamlFragment = fragmentMap; initializeMaps(descriptorYaml); - + urlToYamlFragment.values().forEach(this::initializeMaps); } - + public void setProperties(Properties properties) { this.properties = properties; } @@ -81,10 +84,12 @@ public Iterable getPlans() { public Plan savePlan(Plan p) { try { YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p); - + AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); if (fragment == null) { fragment = fragmentForNewPlan(p); + planToYamlFragment.put(p, fragment); + urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { YamlPlan yamlPlan = planToYamlPlan.get(p); @@ -92,7 +97,7 @@ public Plan savePlan(Plan p) { } planToYamlPlan.put(p, newYamlPlan); writeFragmentToDisk(fragment); - + return p; } catch (MalformedURLException e) { throw new RuntimeException(e); @@ -107,12 +112,11 @@ private void addFragmentEntity(AutomationPack if (!entityList.isEmpty()) { T lastEntity = entityList.get(entityList.size()-1); - String listItemIndent = yaml.substring(yaml.lastIndexOf("\n", lastEntity.getStartOffset()), lastEntity.getStartOffset()); - String indent = listItemIndent.substring(1).replaceAll("-", " "); + String listItemIndent = yaml.substring(lastEntity.getStartListItemOffset(), lastEntity.getStartOffset()); + String indent = " ".repeat(lastEntity.getIndent()); String entityYaml = entityStringWithIndent(indent, newEntity); - String oldString1 = yaml.substring(0, lastEntity.getEndOffset()).trim(); - String newYaml = oldString1 - + listItemIndent + entityYaml + yaml.substring(oldString1.length()); + String newYaml = yaml.substring(0, lastEntity.getEndOffset()) + + listItemIndent + entityYaml + yaml.substring(lastEntity.getEndOffset()); entityList.add(newEntity); fragment.setCurrentYaml(newYaml); } else { @@ -140,23 +144,12 @@ private void modifyFragmentEntity(AutomationPa entityList.replaceAll(plan -> plan == oldEntity ? newEntity : plan); String oldString = fragment.getCurrentYaml(); - + int indent = oldEntity.getIndent(); String indentString = " ".repeat(indent); - - String newArtefactString = entityStringWithIndent(indentString, newEntity); - - int s = oldEntity.getStartOffset(); - int e = oldEntity.getEndOffset(); - int i = oldString.lastIndexOf("\n", e); - - if (i > 0 && oldString.substring(i, e).trim().isEmpty()) { - e = i; - } - - String newString = oldString.substring(0, s) - + newArtefactString + oldString.substring(e); + String newString = oldString.substring(0, oldEntity.getStartOffset()) + + newArtefactString + oldString.substring(oldEntity.getEndOffset()); fragment.setCurrentYaml(newString); } catch (IOException e) { throw new RuntimeException(e); @@ -177,22 +170,9 @@ private String entityStringWithIndent(String indentString, Object entity) throws private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T entity) { String oldString = fragment.getCurrentYaml(); - int s = entity.getStartOffset(); - s = oldString.lastIndexOf("\n", s); - - int e = entity.getEndOffset(); - int i = oldString.lastIndexOf("\n", e); + int s = entityList.isEmpty() ? entity.getStartListOffset() : entity.getStartListItemOffset(); + String newString = oldString.substring(0, s) + oldString.substring(entity.getEndOffset()); - if (i > 0 && oldString.substring(i, e).trim().isEmpty()) { - e = i; - } - - String newString = oldString.substring(0, s) + oldString.substring(e); - - if (entityList.isEmpty()) { - String collectionName = entity.getCollectionName(); - newString = removeEmptyCollection(collectionName, newString); - } fragment.setCurrentYaml(newString); updateFragmentObjectOffsets(fragment); } @@ -205,16 +185,16 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) throws Malforme String planFragmentPath = properties.getProperty(PROPERTY_NEW_PLAN_FRAGMENT_PATH, descriptorYaml.getFragmentUrl().getPath()); planFragmentPath = planFragmentPath.replaceAll("\\%name\\%", sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME))); - + Path path = new File(planFragmentPath).toPath(); if (!path.isAbsolute()) { Path apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) .getParent(); path = apRoot.resolve(path); } - + URL url = path.toUri().toURL(); - + if (urlToYamlFragment.containsKey(url)) return urlToYamlFragment.get(url); AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); fragment.setFragmentUrl(url); @@ -240,9 +220,12 @@ public void removePlan(Plan p) { private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) { try { - AutomationPackageFragmentYaml newFragment = descriptorReader.getYamlObjectMapper().readValue(fragment.getCurrentYaml(), fragment.getClass()); + ObjectMapper mapper = descriptorReader.getYamlObjectMapper(); + JsonParser parser = new PatchingParserDelegate(mapper.createParser(fragment.getCurrentYaml())); + + AutomationPackageFragmentYaml newFragment = descriptorReader.getYamlObjectMapper().readValue(parser, fragment.getClass()); updateFragmentObjectOffsets(newFragment.getPlans(), fragment.getPlans()); - } catch (JsonProcessingException e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -250,7 +233,7 @@ private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) private void updateFragmentObjectOffsets(List newOffsetEntities, List entities) { Iterator newIt = newOffsetEntities.iterator(); Iterator it = entities.iterator(); - + while (newIt.hasNext() && it.hasNext()) { it.next().setPatchingBounds(newIt.next()); } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java new file mode 100644 index 0000000000..59087899d2 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java @@ -0,0 +1,70 @@ +package step.automation.packages.yaml.deserialization; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import step.plans.parser.yaml.PatchableYamlArtefact; + +import java.io.IOException; + +public class PatchableYamlArtefactDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private final JsonDeserializer delegate; + + public PatchableYamlArtefactDeserializer(JsonDeserializer delegate) { + this.delegate = (JsonDeserializer) delegate; + } + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p instanceof PatchingParserDelegate) { + PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; + if (p.getLastClearedToken() == JsonToken.END_OBJECT) { + p.getLastClearedToken(); + } + JsonLocation startList = patchingParser.getDistinctLocationBeforeToken(JsonToken.FIELD_NAME); + int startListItemOffset = 0; + if (p.getLastClearedToken() == JsonToken.END_OBJECT) { + startListItemOffset = (int) patchingParser.getDistinctLocationBeforeToken(JsonToken.END_OBJECT).getCharOffset(); + } else { + startListItemOffset = (int) patchingParser.getDistinctLocationBeforeToken(JsonToken.START_ARRAY).getCharOffset() + 1; + } + JsonLocation startItem = patchingParser.currentLocation(); + T entity = delegate.deserialize(p, ctxt); + entity.setPatchingBounds(startItem, startList, startListItemOffset, patchingParser.getLastDistinctLocation()); + + return entity; + } + return delegate.deserialize(p, ctxt); + } +/* + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException { + if (p instanceof PatchingParserDelegate) { + PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; + JsonLocation before = p.currentLocation(); + T entity = delegate.deserialize(p, ctxt, intoValue); + entity.setPatchingBounds(before, p.currentLocation()); + + return entity; + } + return delegate.deserialize(p, ctxt, intoValue); + }*/ + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException { + JsonDeserializer contextual = delegate; + if (delegate instanceof ContextualDeserializer) { + // make sure to propagate createContextual to the delegate + contextual = ((ContextualDeserializer) contextual).createContextual(ctxt, property); + } + return new PatchableYamlArtefactDeserializer<>((JsonDeserializer) contextual); + } + +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java new file mode 100644 index 0000000000..34235e5450 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java @@ -0,0 +1,41 @@ +package step.automation.packages.yaml.deserialization; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.util.JsonParserDelegate; + +import javax.json.Json; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class PatchingParserDelegate extends JsonParserDelegate { + + private Map distinctLocationBeforeToken = new HashMap<>(); + + private JsonLocation lastDistinctLocation; + + public PatchingParserDelegate(JsonParser d) { + super(d); + } + + @Override + public JsonToken nextToken() throws IOException { + JsonLocation preLocation = super.currentLocation(); + JsonToken token = super.nextToken(); + if (!preLocation.equals(super.currentLocation())) { + lastDistinctLocation = preLocation; + } + distinctLocationBeforeToken.put(token, lastDistinctLocation); + return token; + } + + protected JsonLocation getDistinctLocationBeforeToken(JsonToken token) { + return distinctLocationBeforeToken.get(token); + } + + protected JsonLocation getLastDistinctLocation() { + return lastDistinctLocation; + } +} diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java index 7f48b0f59f..1fb1452984 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java @@ -4,18 +4,22 @@ import com.fasterxml.jackson.core.JsonLocation; public abstract class PatchableYamlArtefact { - + private int startOffset = -1; private int startColumn = -1; private int endOffset = -1; + private int startListItemOffset = -1; + private int startListOffset = -1; @JsonIgnore - public void setPatchingBounds(JsonLocation startLocation, JsonLocation endLocation) { + public void setPatchingBounds(JsonLocation startLocation, JsonLocation startList, int startListItemOffset, JsonLocation endLocation) { startOffset = (int) startLocation.getCharOffset(); endOffset = (int) endLocation.getCharOffset(); startColumn = startLocation.getColumnNr() -1; + this.startListItemOffset =startListItemOffset; + startListOffset = (int) startList.getCharOffset(); } - + @JsonIgnore public int getStartOffset(){ return startOffset; @@ -36,8 +40,20 @@ public void setPatchingBounds(PatchableYamlArtefact newBoundedArtefact) { startOffset = newBoundedArtefact.startOffset; startColumn = newBoundedArtefact.startColumn; endOffset = newBoundedArtefact.endOffset; + startListItemOffset = newBoundedArtefact.startListItemOffset; + startListOffset = newBoundedArtefact.startListOffset; } - + @JsonIgnore abstract public String getCollectionName(); + + @JsonIgnore + public int getStartListItemOffset() { + return startListItemOffset; + } + + @JsonIgnore + public int getStartListOffset() { + return startListOffset; + } } 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 6886a23a58..c0629a811f 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()); diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java index 22a9e915e1..c1654e4df9 100644 --- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java +++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java @@ -61,9 +61,7 @@ public UpgradableYamlPlanDeserializer(Version currentVersion, String jsonSchema, @Override public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - JsonLocation startLocation = p.currentLocation(); JsonNode planJsonNode = p.readValueAsTree(); - JsonLocation endLocation = p.currentLocation(); if (currentVersion != null) { Document yamlPlanDocument = p.getCodec().treeToValue(planJsonNode, Document.class); @@ -119,7 +117,6 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO } YamlPlan yamlPlan = yamlMapper.treeToValue(planJsonNode, YamlPlan.class); - yamlPlan.setPatchingBounds(startLocation, endLocation); return yamlPlan; } From 6b3ad602690ba93b27661c33fcef341f10149b3f Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 19 Mar 2026 09:16:37 +0100 Subject: [PATCH 08/15] SED-4539 generic PatchableYamlArtefactDeserializer --- ...lAutomationPackageSchemaGeneratorTest.java | 2 +- .../AutomationPackageDescriptorReader.java | 3 ++- .../PatchableYamlArtefactDeserializer.java | 23 ++++--------------- .../parser/yaml/PatchableYamlArtefact.java | 9 ++++++++ .../plans/automation/YamlPlainTextPlan.java | 8 ++++++- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/step-automation-packages/step-automation-packages-schema/src/test/java/step/automation/packages/yaml/schema/YamlAutomationPackageSchemaGeneratorTest.java b/step-automation-packages/step-automation-packages-schema/src/test/java/step/automation/packages/yaml/schema/YamlAutomationPackageSchemaGeneratorTest.java index 795f4766a8..b4b540d83d 100644 --- a/step-automation-packages/step-automation-packages-schema/src/test/java/step/automation/packages/yaml/schema/YamlAutomationPackageSchemaGeneratorTest.java +++ b/step-automation-packages/step-automation-packages-schema/src/test/java/step/automation/packages/yaml/schema/YamlAutomationPackageSchemaGeneratorTest.java @@ -35,6 +35,6 @@ public void generateJsonSchema() throws IOException, JsonSchemaPreparationExcept String errorMessage = "Published schema doesn't match to the actual one. To fix the test you need to publish " + "the generated schema printed above and actualize the published schema in current test"; - Assert.assertEquals(errorMessage, publishedSchema, currentSchema); + Assert.assertEquals(errorMessage, publishedSchema.toPrettyString(), currentSchema.toPrettyString()); } } 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 2be2fe3ff4..44d07f9cdf 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 @@ -170,7 +170,8 @@ public void setupModule(SetupContext context) { context.addBeanDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { - if (PatchableYamlArtefact.class.isAssignableFrom(beanDesc.getBeanClass())) { + if (PatchableYamlArtefact.class.isAssignableFrom(beanDesc.getBeanClass()) + && !beanDesc.getBeanClass().equals(PatchableYamlArtefact.class)) { return new PatchableYamlArtefactDeserializer<>(deserializer); } return super.modifyDeserializer(config, beanDesc, deserializer); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java index 59087899d2..8502f9da59 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import step.plans.parser.yaml.PatchableYamlArtefact; import java.io.IOException; @@ -24,9 +25,6 @@ public PatchableYamlArtefactDeserializer(JsonDeserializer delegate) { public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (p instanceof PatchingParserDelegate) { PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; - if (p.getLastClearedToken() == JsonToken.END_OBJECT) { - p.getLastClearedToken(); - } JsonLocation startList = patchingParser.getDistinctLocationBeforeToken(JsonToken.FIELD_NAME); int startListItemOffset = 0; if (p.getLastClearedToken() == JsonToken.END_OBJECT) { @@ -42,29 +40,18 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOExcepti } return delegate.deserialize(p, ctxt); } -/* - @Override - public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException { - if (p instanceof PatchingParserDelegate) { - PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; - JsonLocation before = p.currentLocation(); - T entity = delegate.deserialize(p, ctxt, intoValue); - entity.setPatchingBounds(before, p.currentLocation()); - - return entity; - } - return delegate.deserialize(p, ctxt, intoValue); - }*/ @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { JsonDeserializer contextual = delegate; - if (delegate instanceof ContextualDeserializer) { + if (contextual instanceof ContextualDeserializer) { // make sure to propagate createContextual to the delegate contextual = ((ContextualDeserializer) contextual).createContextual(ctxt, property); } + if (contextual instanceof ResolvableDeserializer) { + ((ResolvableDeserializer) contextual).resolve(ctxt); + } return new PatchableYamlArtefactDeserializer<>((JsonDeserializer) contextual); } - } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java index 1fb1452984..ec8acaaae3 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java @@ -5,10 +5,19 @@ public abstract class PatchableYamlArtefact { + @JsonIgnore private int startOffset = -1; + + @JsonIgnore private int startColumn = -1; + + @JsonIgnore private int endOffset = -1; + + @JsonIgnore private int startListItemOffset = -1; + + @JsonIgnore private int startListOffset = -1; @JsonIgnore diff --git a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java index 2b7157c612..ff57566ea3 100644 --- a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java +++ b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java @@ -19,10 +19,11 @@ package step.plans.automation; import step.plans.nl.RootArtefactType; +import step.plans.parser.yaml.PatchableYamlArtefact; import java.util.List; -public class YamlPlainTextPlan { +public class YamlPlainTextPlan extends PatchableYamlArtefact { private String name; @@ -63,4 +64,9 @@ public RootArtefactType getRootType() { public void setRootType(RootArtefactType rootType) { this.rootType = rootType; } + + @Override + public String getCollectionName() { + return "plainTextPlan"; + } } From 177b99d25bd735257ca956c9e35128710c65d4c8 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 19 Mar 2026 10:24:11 +0100 Subject: [PATCH 09/15] SED-4539 fix trivial review issues --- .../java/step/automation/packages/AutomationPackageReader.java | 2 +- .../step/automation/packages/JavaAutomationPackageReader.java | 2 +- .../packages/yaml/deserialization/PatchingParserDelegate.java | 1 - .../src/main/java/step/plans/automation/YamlPlainTextPlan.java | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) 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 de6fb4bc86..82a0fa73b3 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 @@ -186,7 +186,7 @@ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragment fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); } catch (IOException e) { - throw new RuntimeException(e); + throw new AutomationPackageReadingException("Failed to read automation package for editing", e); } } 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 51317d913d..ea81a48d11 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 @@ -245,7 +245,7 @@ private static boolean isCompositeFunction(Keyword annotation) { * @param automationPackage the JAR file to be read * @param apVersion the automation package version * @param keywordLib the package library file - * @return the automation package content raed from the provided files + * @return the automation package content read from the provided files * @throws AutomationPackageReadingException in case of error */ public AutomationPackageContent readAutomationPackageFromJarFile(File automationPackage, String apVersion, File keywordLib) throws AutomationPackageReadingException { diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java index 34235e5450..e01d3eecca 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.util.JsonParserDelegate; -import javax.json.Json; import java.io.IOException; import java.util.HashMap; import java.util.Map; diff --git a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java index ff57566ea3..efa792f003 100644 --- a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java +++ b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java @@ -67,6 +67,6 @@ public void setRootType(RootArtefactType rootType) { @Override public String getCollectionName() { - return "plainTextPlan"; + return AutomationPackagePlainTextPlanJsonSchema.FIELD_NAME_IN_AP; } } From 9caa1f5cb2a52291d274392b71eaa82f64d29c13 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 19 Mar 2026 10:44:27 +0100 Subject: [PATCH 10/15] SED-4539 fix trivial review issues --- .../step/automation/packages/JavaAutomationPackageReader.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 ea81a48d11..1c8068e575 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 @@ -9,7 +9,6 @@ import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; import step.core.scanner.AnnotationScanner; -import step.engine.plugins.LocalFunctionPlugin; import step.functions.Function; import step.functions.manager.FunctionManagerImpl; import step.handlers.javahandler.Keyword; @@ -258,7 +257,7 @@ public AutomationPackageContent readAutomationPackageFromJarFile(File automation /** Convenient method for test * @param automationPackage the JAR file to be read - * @return the automation package content raed from the provided files + * @return the automation package content read from the provided files for editing * @throws AutomationPackageReadingException in case of error */ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragmentManager(File automationPackage) throws AutomationPackageReadingException { From 12b840afc6c5489c3db4a71ee5d85938c8390121 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 19 Mar 2026 12:22:52 +0100 Subject: [PATCH 11/15] SED-4539 remove unnecessary serializationregistryaware --- .../AutomationPackageDescriptorReader.java | 12 ---------- ...tionPackageSerializationRegistryAware.java | 24 ------------------- .../StepYamlDeserializersScanner.java | 20 ++-------------- .../UpgradableYamlPlanDeserializer.java | 4 +--- 4 files changed, 3 insertions(+), 57 deletions(-) delete mode 100644 step-core/src/main/java/step/automation/packages/deserialization/AutomationPackageSerializationRegistryAware.java 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 44d07f9cdf..d807da7125 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 @@ -20,7 +20,6 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -32,7 +31,6 @@ import step.artefacts.handlers.JsonSchemaValidator; import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistryAware; import step.automation.packages.yaml.deserialization.PatchableYamlArtefactDeserializer; import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; @@ -40,7 +38,6 @@ import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.accessors.DefaultJacksonMapperProvider; -import step.core.yaml.deserializers.StepYamlDeserializersScanner; import step.plans.parser.yaml.PatchableYamlArtefact; import step.plans.parser.yaml.YamlPlanReader; import step.plans.parser.yaml.model.YamlPlanVersions; @@ -182,15 +179,6 @@ public JsonDeserializer modifyDeserializer(DeserializationConfig config, Bean // register deserializers to read yaml plans planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); - - // add annotated jackson deserializers - StepYamlDeserializersScanner.addAllDeserializerAddonsToModule(module, yamlMapper, List.of(stepYamlDeserializer -> { - if (stepYamlDeserializer instanceof AutomationPackageSerializationRegistryAware) { - ((AutomationPackageSerializationRegistryAware) stepYamlDeserializer).setSerializationRegistry(serializationRegistry); - } - })); - - yamlMapper.registerModule(module); return yamlMapper; diff --git a/step-core/src/main/java/step/automation/packages/deserialization/AutomationPackageSerializationRegistryAware.java b/step-core/src/main/java/step/automation/packages/deserialization/AutomationPackageSerializationRegistryAware.java deleted file mode 100644 index bdcbbe372d..0000000000 --- a/step-core/src/main/java/step/automation/packages/deserialization/AutomationPackageSerializationRegistryAware.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, 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.deserialization; - -public interface AutomationPackageSerializationRegistryAware { - - void setSerializationRegistry(AutomationPackageSerializationRegistry registry); -} diff --git a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializersScanner.java b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializersScanner.java index dd26a10a3a..8051aba5eb 100644 --- a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializersScanner.java +++ b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializersScanner.java @@ -36,7 +36,7 @@ public class StepYamlDeserializersScanner { /** * Scans and returns all {@link StepYamlDeserializer} classes annotated with {@link StepYamlDeserializerAddOn} */ - public static List> scanDeserializerAddons(ObjectMapper yamlObjectMapper, List>> configurators) { + public static List> scanDeserializerAddons(ObjectMapper yamlObjectMapper) { List> result = new ArrayList<>(); List> annotatedClasses = new ArrayList<>(CachedAnnotationScanner.getClassesWithAnnotation(StepYamlDeserializerAddOn.LOCATION, StepYamlDeserializerAddOn.class, Thread.currentThread().getContextClassLoader())); for (Class annotatedClass : annotatedClasses) { @@ -45,11 +45,6 @@ public static List> scanDeserializerAddons(ObjectMapper yaml Arrays.stream(annotation.targetClasses()).forEach(aClass -> { try { StepYamlDeserializer newDeserializer = (StepYamlDeserializer) annotatedClass.getConstructor(ObjectMapper.class).newInstance(yamlObjectMapper); - if (configurators != null) { - for (Consumer> configurator : configurators) { - configurator.accept(newDeserializer); - } - } result.add(new DeserializerBind<>((Class) aClass, newDeserializer)); } catch (Exception e) { throw new RuntimeException("Cannot prepare deserializer", e); @@ -61,20 +56,9 @@ public static List> scanDeserializerAddons(ObjectMapper yaml return result; } - /** - * Scans and returns all {@link StepYamlDeserializer} classes annotated with {@link StepYamlDeserializerAddOn} - */ - public static List> scanDeserializerAddons(ObjectMapper yamlObjectMapper) { - return scanDeserializerAddons(yamlObjectMapper, null); - } - public static SimpleModule addAllDeserializerAddonsToModule(SimpleModule module, ObjectMapper yamlObjectMapper) { - return addAllDeserializerAddonsToModule(module, yamlObjectMapper, null); - } - - public static SimpleModule addAllDeserializerAddonsToModule(SimpleModule module, ObjectMapper yamlObjectMapper, List>> configurators) { SimpleModule res = module; - for (StepYamlDeserializersScanner.DeserializerBind deser : StepYamlDeserializersScanner.scanDeserializerAddons(yamlObjectMapper, configurators)) { + for (StepYamlDeserializersScanner.DeserializerBind deser : StepYamlDeserializersScanner.scanDeserializerAddons(yamlObjectMapper)) { res = module.addDeserializer((Class) deser.clazz, deser.deserializer); } return res; diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java index c1654e4df9..06d9735d80 100644 --- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java +++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/UpgradableYamlPlanDeserializer.java @@ -18,7 +18,6 @@ ******************************************************************************/ package step.plans.parser.yaml.deserializers; -import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -116,8 +115,7 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO } } - YamlPlan yamlPlan = yamlMapper.treeToValue(planJsonNode, YamlPlan.class); - return yamlPlan; + return yamlMapper.treeToValue(planJsonNode, YamlPlan.class); } } From 3b47d4c523aa5c64a96226ad8e625a7a4bdd396e Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 20 Mar 2026 11:24:38 +0100 Subject: [PATCH 12/15] SED-4539 Exception handling --- .../AutomationPackageCollectionTest.java | 22 +++ ...omationPackageConcurrentEditException.java | 7 + .../AutomationPackageUpdateException.java | 11 ++ ...AutomationPackageWriteToDiskException.java | 7 + .../AutomationPackageYamlFragmentManager.java | 158 ++++++++++-------- 5 files changed, 131 insertions(+), 74 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageWriteToDiskException.java 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 index 88f0840b62..a32d6fd3ee 100644 --- 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 @@ -32,6 +32,7 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.AutomationPackageConcurrentEditException; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.automation.packages.yaml.YamlAutomationPackageVersions; import step.core.dynamicbeans.DynamicValue; @@ -41,6 +42,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -119,6 +121,26 @@ public void testPlanModify() throws IOException { } + @Test + public void testPlanModifyWithConcurrentEdit() 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();"); + + Files.writeString(destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"), "This file was edited", StandardCharsets.UTF_8); + + assertThrows(AutomationPackageConcurrentEditException.class, () -> planCollection.save(plan)); + + } + + @Test public void testPlanRenameExisting() throws IOException { Optional optionalPlan = planCollection.find(Filters.equals("attributes.name", "Test Plan"), null, null, null, 100).findFirst(); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java new file mode 100644 index 0000000000..5f9feb513e --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java @@ -0,0 +1,7 @@ +package step.automation.packages.yaml; + +public class AutomationPackageConcurrentEditException extends AutomationPackageUpdateException { + public AutomationPackageConcurrentEditException(String s) { + super(s); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java new file mode 100644 index 0000000000..9024882c50 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java @@ -0,0 +1,11 @@ +package step.automation.packages.yaml; + +public class AutomationPackageUpdateException extends RuntimeException { + public AutomationPackageUpdateException(String s, Exception e) { + super(s, e); + } + + public AutomationPackageUpdateException(String s) { + super(s); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageWriteToDiskException.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageWriteToDiskException.java new file mode 100644 index 0000000000..5ebf9deee5 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageWriteToDiskException.java @@ -0,0 +1,7 @@ +package step.automation.packages.yaml; + +public class AutomationPackageWriteToDiskException extends AutomationPackageUpdateException { + public AutomationPackageWriteToDiskException(String s, Exception e) { + super(s, e); + } +} 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 index 4e24471faf..987062c859 100644 --- 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 @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.FileUtils; +import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; @@ -37,8 +38,10 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.text.MessageFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; public class AutomationPackageYamlFragmentManager { @@ -82,106 +85,97 @@ public Iterable getPlans() { } public Plan savePlan(Plan p) { - try { - YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p); - - AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); - if (fragment == null) { - fragment = fragmentForNewPlan(p); - planToYamlFragment.put(p, fragment); - urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); - addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); - } else { - YamlPlan yamlPlan = planToYamlPlan.get(p); - modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); - } - planToYamlPlan.put(p, newYamlPlan); - writeFragmentToDisk(fragment); + YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p); - return p; - } catch (MalformedURLException e) { - throw new RuntimeException(e); + AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); + if (fragment == null) { + fragment = fragmentForNewPlan(p); + planToYamlFragment.put(p, fragment); + urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); + addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); + } else { + YamlPlan yamlPlan = planToYamlPlan.get(p); + modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); } + planToYamlPlan.put(p, newYamlPlan); + + return p; } private void addFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T newEntity) { - try { - String collectionName = newEntity.getCollectionName(); - String yaml = fragment.getCurrentYaml(); + String collectionName = newEntity.getCollectionName(); + + if (!entityList.isEmpty()) { + T lastEntity = entityList.get(entityList.size()-1); + entityList.add(newEntity); + + writeFragmentToDisk(fragment, yaml -> { - if (!entityList.isEmpty()) { - T lastEntity = entityList.get(entityList.size()-1); String listItemIndent = yaml.substring(lastEntity.getStartListItemOffset(), lastEntity.getStartOffset()); String indent = " ".repeat(lastEntity.getIndent()); String entityYaml = entityStringWithIndent(indent, newEntity); - String newYaml = yaml.substring(0, lastEntity.getEndOffset()) - + listItemIndent + entityYaml + yaml.substring(lastEntity.getEndOffset()); - entityList.add(newEntity); - fragment.setCurrentYaml(newYaml); - } else { - entityList.add(newEntity); + + return yaml.substring(0, lastEntity.getEndOffset()) + + listItemIndent + entityYaml + yaml.substring(lastEntity.getEndOffset()); + }); + + } else { + entityList.add(newEntity); + writeFragmentToDisk(fragment, yaml -> { + String listYaml = collectionName + ":\n" - + entityStringWithIndent("", entityList); + + entityStringWithIndent("", entityList); if (yaml == null) { - yaml = "---\n" + listYaml; + return "---\n" + listYaml; } else { - yaml = removeEmptyCollection(collectionName, yaml).trim() + return removeEmptyCollection(collectionName, yaml).trim() + "\n" + listYaml; } - fragment.setCurrentYaml(yaml); - } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); + }); } - updateFragmentObjectOffsets(fragment); } - private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T oldEntity, T newEntity){ - try { - entityList.replaceAll(plan -> plan == oldEntity ? newEntity : plan); - - String oldString = fragment.getCurrentYaml(); - + private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T oldEntity, T newEntity) { + entityList.replaceAll(plan -> plan == oldEntity ? newEntity : plan); + writeFragmentToDisk(fragment, yaml -> { int indent = oldEntity.getIndent(); String indentString = " ".repeat(indent); String newArtefactString = entityStringWithIndent(indentString, newEntity); - String newString = oldString.substring(0, oldEntity.getStartOffset()) - + newArtefactString + oldString.substring(oldEntity.getEndOffset()); - fragment.setCurrentYaml(newString); - } catch (IOException e) { - throw new RuntimeException(e); - } + return yaml.substring(0, oldEntity.getStartOffset()) + + newArtefactString + yaml.substring(oldEntity.getEndOffset()); - updateFragmentObjectOffsets(fragment); + }); } - private String entityStringWithIndent(String indentString, Object entity) throws JsonProcessingException { - return descriptorReader - .getYamlObjectMapper() - .writeValueAsString(entity) - .replaceAll("---\n", "") - .trim() - .replaceAll("\n", "\n" + indentString); + private String entityStringWithIndent(String indentString, Object entity) { + try { + return descriptorReader + .getYamlObjectMapper() + .writeValueAsString(entity) + .replaceAll("---\n", "") + .trim() + .replaceAll("\n", "\n" + indentString); + } catch (JsonProcessingException e) { + throw new AutomationPackageUpdateException("Error Serializing new object", e); + } } private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T entity) { - String oldString = fragment.getCurrentYaml(); - - int s = entityList.isEmpty() ? entity.getStartListOffset() : entity.getStartListItemOffset(); - String newString = oldString.substring(0, s) + oldString.substring(entity.getEndOffset()); - - fragment.setCurrentYaml(newString); - updateFragmentObjectOffsets(fragment); + entityList.remove(entity); + writeFragmentToDisk(fragment, yaml -> { + int s = entityList.isEmpty() ? entity.getStartListOffset() : entity.getStartListItemOffset(); + return yaml.substring(0, s) + yaml.substring(entity.getEndOffset()); + }); } private String removeEmptyCollection(String collectionName, String yaml) { return yaml.replaceAll("\n*" + collectionName + ":.*\n*", "\n"); } - private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) throws MalformedURLException { + private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { String planFragmentPath = properties.getProperty(PROPERTY_NEW_PLAN_FRAGMENT_PATH, descriptorYaml.getFragmentUrl().getPath()); planFragmentPath = planFragmentPath.replaceAll("\\%name\\%", sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME))); @@ -193,12 +187,18 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) throws Malforme path = apRoot.resolve(path); } - URL url = path.toUri().toURL(); + try { + URL url = path.toUri().toURL(); + + + if (urlToYamlFragment.containsKey(url)) return urlToYamlFragment.get(url); + AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); + fragment.setFragmentUrl(url); + return fragment; + } catch (MalformedURLException e) { + throw new AutomationPackageUpdateException(MessageFormat.format("Error creating path for new fragment: {0}", path), e); + } - if (urlToYamlFragment.containsKey(url)) return urlToYamlFragment.get(url); - AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); - fragment.setFragmentUrl(url); - return fragment; } public String sanitizeFilename(String inputName) { @@ -215,7 +215,6 @@ public void removePlan(Plan p) { planToYamlFragment.remove(p); removeFragmentEntity(fragment, fragment.getPlans(), yamlPlan); - writeFragmentToDisk(fragment); } private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) { @@ -226,7 +225,7 @@ private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) AutomationPackageFragmentYaml newFragment = descriptorReader.getYamlObjectMapper().readValue(parser, fragment.getClass()); updateFragmentObjectOffsets(newFragment.getPlans(), fragment.getPlans()); } catch (IOException e) { - throw new RuntimeException(e); + throw new AutomationPackageUpdateException("Error re-writing automation package fragment {0}", e); } } @@ -237,14 +236,25 @@ private void updateFragmentObjectOffsets(List< while (newIt.hasNext() && it.hasNext()) { it.next().setPatchingBounds(newIt.next()); } + + if (newIt.hasNext() || it.hasNext()) { + throw new AutomationPackageUpdateException("Error with updating fragment object offsets. Inconsistent collection size."); + } } - private void writeFragmentToDisk(AutomationPackageFragmentYaml fragment) { + private synchronized void writeFragmentToDisk(AutomationPackageFragmentYaml fragment, Function yamlModifier) { try { File file = new File(fragment.getFragmentUrl().toURI()); + if (file.exists()) { + if (!fragment.getCurrentYaml().equals(FileUtils.readFileToString(file, StandardCharsets.UTF_8))) { + throw new AutomationPackageConcurrentEditException(MessageFormat.format("Automation package fragment {0} was edited outside the editor.", fragment.getFragmentUrl())); + } + } + fragment.setCurrentYaml(yamlModifier.apply(fragment.getCurrentYaml())); + updateFragmentObjectOffsets(fragment); FileUtils.writeStringToFile(file, fragment.getCurrentYaml(), StandardCharsets.UTF_8); } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); + throw new AutomationPackageWriteToDiskException(MessageFormat.format("Error when writing automation package fragment {0} back to disk.", fragment.getFragmentUrl()), e); } } } From a0507b41e2995a2ce663d3f0de2ab37d92e14d9d Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 20 Mar 2026 11:31:46 +0100 Subject: [PATCH 13/15] SED-4539 Exception handling --- .../AutomationPackageCollectionTest.java | 8 ++----- .../packages/AutomationPackageReader.java | 8 +++---- .../AutomationPackageYamlFragmentManager.java | 21 +++++++++---------- 3 files changed, 16 insertions(+), 21 deletions(-) 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 index a32d6fd3ee..df3a96df65 100644 --- 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 @@ -24,8 +24,6 @@ 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.artefacts.Sequence; import step.automation.packages.AutomationPackageHookRegistry; @@ -52,17 +50,15 @@ 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 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"); private AutomationPackageYamlFragmentManager fragmentManager; - public AutomationPackageCollectionTest() throws AutomationPackageReadingException { + public AutomationPackageCollectionTest() { AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); 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 82a0fa73b3..72b07722f9 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 @@ -123,7 +123,7 @@ protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescr // apply imported fragments recursively if (descriptor != null) { - Map fragmentMap = new HashMap<>(); + Map fragmentMap = new HashMap<>(); fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); } return res; @@ -182,7 +182,7 @@ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragment AutomationPackageDescriptorYaml descriptor = reader.readAutomationPackageDescriptor(inputStream, archive.getOriginalFileName()); descriptor.setFragmentUrl(descriptorURL); AutomationPackageContent res = newContentInstance(); - Map fragmentMap = new HashMap<>(); + Map fragmentMap = new HashMap<>(); fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); } catch (IOException e) { @@ -190,7 +190,7 @@ public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragment } } - private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive, Map fragmentYamlMap) throws AutomationPackageReadingException { + private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive, Map fragmentYamlMap) throws AutomationPackageReadingException { fillContentSections(targetPackage, fragment, archive); if (!fragment.getFragments().isEmpty()) { @@ -199,7 +199,7 @@ private void fillAutomationPackageWithImportedFragments(AutomationPackageContent for (URL resource : resources) { try (InputStream fragmentYamlStream = resource.openStream()) { fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, importedFragmentReference, archive.getAutomationPackageName()); - fragmentYamlMap.put(resource, fragment); + fragmentYamlMap.put(resource.toString(), fragment); fragment.setFragmentUrl(resource); fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive, fragmentYamlMap); } catch (IOException e) { 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 index 987062c859..f766dcac34 100644 --- 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 @@ -22,7 +22,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.FileUtils; -import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; @@ -51,20 +50,20 @@ public class AutomationPackageYamlFragmentManager { private final Map planToYamlPlan = new ConcurrentHashMap<>(); private final Map planToYamlFragment = new ConcurrentHashMap<>(); - private final Map urlToYamlFragment; + private final Map pathToYamlFragment; private Properties properties = new Properties(); private final AutomationPackageFragmentYaml descriptorYaml; - public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, Map fragmentMap, AutomationPackageDescriptorReader descriptorReader) { + public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, Map fragmentMap, AutomationPackageDescriptorReader descriptorReader) { this.descriptorReader = descriptorReader; this.descriptorYaml = descriptorYaml; - urlToYamlFragment = fragmentMap; + pathToYamlFragment = fragmentMap; initializeMaps(descriptorYaml); - urlToYamlFragment.values().forEach(this::initializeMaps); + pathToYamlFragment.values().forEach(this::initializeMaps); } public void setProperties(Properties properties) { @@ -72,12 +71,12 @@ public void setProperties(Properties properties) { } public void initializeMaps(AutomationPackageFragmentYaml fragment) { - urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); + pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); for (YamlPlan p: fragment.getPlans()) { Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(p); planToYamlPlan.put(plan, p); planToYamlFragment.put(plan, fragment); - }; + } } public Iterable getPlans() { @@ -91,7 +90,7 @@ public Plan savePlan(Plan p) { if (fragment == null) { fragment = fragmentForNewPlan(p); planToYamlFragment.put(p, fragment); - urlToYamlFragment.put(fragment.getFragmentUrl(), fragment); + pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { YamlPlan yamlPlan = planToYamlPlan.get(p); @@ -178,7 +177,7 @@ private String removeEmptyCollection(String collectionName, String yaml) { private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { String planFragmentPath = properties.getProperty(PROPERTY_NEW_PLAN_FRAGMENT_PATH, descriptorYaml.getFragmentUrl().getPath()); - planFragmentPath = planFragmentPath.replaceAll("\\%name\\%", sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME))); + planFragmentPath = planFragmentPath.replaceAll("%name%", sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME))); Path path = new File(planFragmentPath).toPath(); if (!path.isAbsolute()) { @@ -191,7 +190,7 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { URL url = path.toUri().toURL(); - if (urlToYamlFragment.containsKey(url)) return urlToYamlFragment.get(url); + if (pathToYamlFragment.containsKey(url.toString())) return pathToYamlFragment.get(url.toString()); AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); fragment.setFragmentUrl(url); return fragment; @@ -202,7 +201,7 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { } public String sanitizeFilename(String inputName) { - return inputName.replaceAll("[^a-zA-Z0-9-_\\.]", "_"); + return inputName.replaceAll("[^a-zA-Z0-9-_.]", "_"); } public void removePlan(Plan p) { From 3169fc01133eb94b2afe2e4853bed0c88f4243e6 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 20 Mar 2026 16:31:04 +0100 Subject: [PATCH 14/15] SED-4539 Define Collections as Patchable --- .../automation-package.yml | 2 +- .../expected/descriptorAfterAdd.yml | 16 ++-- .../AutomationPackageDescriptorReader.java | 26 ++++-- .../AutomationPackageYamlFragmentManager.java | 33 ++++--- .../packages/yaml/LocatedJsonNode.java | 47 ++++++++++ .../yaml/LocatedYamlObjectFactory.java | 19 ++++ .../LocationAwareTreeTraversingParser.java | 28 ++++++ .../PatchableYamlListDeserializer.java | 55 ++++++++++++ ...va => PatchableYamlModelDeserializer.java} | 10 +-- .../PatchingParserDelegate.java | 22 ++++- ...AbstractAutomationPackageFragmentYaml.java | 15 ++-- .../model/AutomationPackageFragmentYaml.java | 11 ++- .../step/core/yaml/AbstractYamlModel.java | 2 + .../core/yaml/PatchableAbstractYamlModel.java | 81 +++++++++++++++++ .../step/core/yaml/PatchableYamlList.java | 90 +++++++++++++++++++ .../step/core/yaml/PatchableYamlModel.java | 18 ++++ .../parser/yaml/PatchableYamlArtefact.java | 68 -------------- .../java/step/plans/parser/yaml/YamlPlan.java | 9 +- .../plans/automation/YamlPlainTextPlan.java | 11 +-- 19 files changed, 429 insertions(+), 134 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java rename step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/{PatchableYamlArtefactDeserializer.java => PatchableYamlModelDeserializer.java} (82%) create mode 100644 step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java create mode 100644 step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java create mode 100644 step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java delete mode 100644 step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java 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 index 597515a1de..c80fc11302 100644 --- 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 @@ -19,4 +19,4 @@ fragments: - "schedules.yml" - "parameters.yml" - "parameters2.yml" - - "unknown.yml" \ No newline at end of file + - "unknown.yml" diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml index 9afeca68e0..ac6f041ffe 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml @@ -12,13 +12,6 @@ alertingRules: predicate: BindingValueEqualsPredicate: value: "myValue" -fragments: - - "keywords.yml" - - "plans/*.yml" - - "schedules.yml" - - "parameters.yml" - - "parameters2.yml" - - "unknown.yml" plans: - version: "1.2.0" name: "New Name" @@ -28,4 +21,11 @@ plans: - echo: text: "Hello World" agents: null - categories: null \ No newline at end of file + categories: null +fragments: + - "keywords.yml" + - "plans/*.yml" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.yml" 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 d807da7125..6c84eb6c7b 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 @@ -18,10 +18,11 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; +import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import org.apache.commons.lang3.StringUtils; @@ -31,14 +32,15 @@ import step.artefacts.handlers.JsonSchemaValidator; import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.automation.packages.yaml.deserialization.PatchableYamlArtefactDeserializer; +import step.automation.packages.yaml.deserialization.PatchableYamlListDeserializer; +import step.automation.packages.yaml.deserialization.PatchableYamlModelDeserializer; import step.automation.packages.yaml.deserialization.PatchingParserDelegate; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.accessors.DefaultJacksonMapperProvider; -import step.plans.parser.yaml.PatchableYamlArtefact; +import step.core.yaml.PatchableYamlModel; import step.plans.parser.yaml.YamlPlanReader; import step.plans.parser.yaml.model.YamlPlanVersions; import step.plans.parser.yaml.schema.YamlPlanValidationException; @@ -108,7 +110,8 @@ protected T readAutomationPackageYamlF } } - JsonParser parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString)); + PatchingParserDelegate parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString)); + yamlObjectMapper.setNodeFactory(new LocatedYamlObjectFactory(parser)); T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(parser, targetClass); res.setCurrentYaml(yamlDescriptorString); logAfterRead(packageName, res); @@ -153,7 +156,6 @@ public ObjectMapper createYamlObjectMapper() { // Disable native type id to enable conversion to generic Documents yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); ObjectMapper yamlMapper = DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); - yamlMapper.setInjectableValues(new InjectableValues.Std() .addValue(ObjectMapper.class, yamlMapper) .addValue(AutomationPackageSerializationRegistry.class, serializationRegistry) @@ -167,12 +169,20 @@ public void setupModule(SetupContext context) { context.addBeanDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { - if (PatchableYamlArtefact.class.isAssignableFrom(beanDesc.getBeanClass()) - && !beanDesc.getBeanClass().equals(PatchableYamlArtefact.class)) { - return new PatchableYamlArtefactDeserializer<>(deserializer); + if (PatchableYamlModel.class.isAssignableFrom(beanDesc.getBeanClass()) + && !beanDesc.getBeanClass().equals(PatchableYamlModel.class)) { + return new PatchableYamlModelDeserializer<>(deserializer); } return super.modifyDeserializer(config, beanDesc, deserializer); } + + @Override + public JsonDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer deserializer) { + if (deserializer instanceof CollectionDeserializer) { + return new PatchableYamlListDeserializer((CollectionDeserializer) deserializer); + } + return deserializer; + } }); } }; 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 index f766dcac34..15c0fe9c49 100644 --- 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 @@ -28,7 +28,8 @@ import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.accessors.AbstractOrganizableObject; import step.core.plans.Plan; -import step.plans.parser.yaml.PatchableYamlArtefact; +import step.core.yaml.PatchableYamlList; +import step.core.yaml.PatchableYamlModel; import step.plans.parser.yaml.YamlPlan; import java.io.*; @@ -91,7 +92,7 @@ public Plan savePlan(Plan p) { fragment = fragmentForNewPlan(p); planToYamlFragment.put(p, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); + addFragmentEntity(fragment, YamlPlan.PLANS_ENTITY_NAME, fragment.getPlans(), newYamlPlan); } else { YamlPlan yamlPlan = planToYamlPlan.get(p); modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); @@ -101,17 +102,14 @@ public Plan savePlan(Plan p) { return p; } - private void addFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T newEntity) { - - String collectionName = newEntity.getCollectionName(); - + private void addFragmentEntity(AutomationPackageFragmentYaml fragment, String collectionName, PatchableYamlList entityList, T newEntity) { if (!entityList.isEmpty()) { T lastEntity = entityList.get(entityList.size()-1); entityList.add(newEntity); writeFragmentToDisk(fragment, yaml -> { - String listItemIndent = yaml.substring(lastEntity.getStartListItemOffset(), lastEntity.getStartOffset()); + String listItemIndent = yaml.substring(lastEntity.getStartFieldOffset(), lastEntity.getStartOffset()); String indent = " ".repeat(lastEntity.getIndent()); String entityYaml = entityStringWithIndent(indent, newEntity); @@ -123,21 +121,21 @@ private void addFragmentEntity(AutomationPack entityList.add(newEntity); writeFragmentToDisk(fragment, yaml -> { - String listYaml = collectionName + ":\n" + String listYaml = collectionName + ":\n" + entityStringWithIndent("", entityList); if (yaml == null) { return "---\n" + listYaml; } else { - return removeEmptyCollection(collectionName, yaml).trim() - + "\n" + listYaml; + return yaml.substring(0, entityList.getStartFieldOffset()) + + listYaml + yaml.substring(entityList.getEndOffset()); } }); } } - private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T oldEntity, T newEntity) { + private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T oldEntity, T newEntity) { entityList.replaceAll(plan -> plan == oldEntity ? newEntity : plan); writeFragmentToDisk(fragment, yaml -> { int indent = oldEntity.getIndent(); @@ -162,18 +160,14 @@ private String entityStringWithIndent(String indentString, Object entity) { } } - private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, List entityList, T entity) { + private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T entity) { entityList.remove(entity); writeFragmentToDisk(fragment, yaml -> { - int s = entityList.isEmpty() ? entity.getStartListOffset() : entity.getStartListItemOffset(); + int s = entityList.isEmpty() ? entityList.getStartFieldOffset() : entity.getStartFieldOffset(); return yaml.substring(0, s) + yaml.substring(entity.getEndOffset()); }); } - private String removeEmptyCollection(String collectionName, String yaml) { - return yaml.replaceAll("\n*" + collectionName + ":.*\n*", "\n"); - } - private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { String planFragmentPath = properties.getProperty(PROPERTY_NEW_PLAN_FRAGMENT_PATH, descriptorYaml.getFragmentUrl().getPath()); @@ -222,13 +216,14 @@ private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) JsonParser parser = new PatchingParserDelegate(mapper.createParser(fragment.getCurrentYaml())); AutomationPackageFragmentYaml newFragment = descriptorReader.getYamlObjectMapper().readValue(parser, fragment.getClass()); + updateFragmentObjectOffsets(newFragment.getPlans(), fragment.getPlans()); } catch (IOException e) { throw new AutomationPackageUpdateException("Error re-writing automation package fragment {0}", e); } } - private void updateFragmentObjectOffsets(List newOffsetEntities, List entities) { + private void updateFragmentObjectOffsets(PatchableYamlList newOffsetEntities, PatchableYamlList entities) { Iterator newIt = newOffsetEntities.iterator(); Iterator it = entities.iterator(); @@ -239,6 +234,8 @@ private void updateFragmentObjectOffsets(List< if (newIt.hasNext() || it.hasNext()) { throw new AutomationPackageUpdateException("Error with updating fragment object offsets. Inconsistent collection size."); } + + entities.setPatchingBounds(newOffsetEntities); } private synchronized void writeFragmentToDisk(AutomationPackageFragmentYaml fragment, Function yamlModifier) { diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java new file mode 100644 index 0000000000..23edd99ef8 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java @@ -0,0 +1,47 @@ +package step.automation.packages.yaml; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TreeTraversingParser; + +import javax.json.Json; + +public class LocatedJsonNode extends ObjectNode { + + private JsonLocation startLocation; + private JsonLocation endLocation; + + public LocatedJsonNode(JsonNodeFactory nodeFactory) { + super(nodeFactory); + } + + @Override + public JsonParser traverse(ObjectCodec codec) { + // wrap the default TreeTraversingParser with your own delegate + return new LocationAwareTreeTraversingParser(super.traverse(codec), this); + } + + @Override + public JsonParser traverse() { + return new LocationAwareTreeTraversingParser(super.traverse(), this); + } + + public JsonLocation getEndLocation() { + return endLocation; + } + + public JsonLocation getStartLocation() { + return startLocation; + } + + public void setEndLocation(JsonLocation endLocation) { + this.endLocation = endLocation; + } + + public void setStartLocation(JsonLocation startLocation) { + this.startLocation = startLocation; + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java new file mode 100644 index 0000000000..5dd759fc2d --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java @@ -0,0 +1,19 @@ +package step.automation.packages.yaml; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import step.automation.packages.yaml.deserialization.PatchingParserDelegate; + +public class LocatedYamlObjectFactory extends JsonNodeFactory { + private final PatchingParserDelegate parser; + + public LocatedYamlObjectFactory(PatchingParserDelegate parser) { + this.parser = parser; + + } + + @Override + public ObjectNode objectNode() { + return parser.setCurrentObjectNode(new LocatedJsonNode(this)); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java new file mode 100644 index 0000000000..b7eedf8aa4 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java @@ -0,0 +1,28 @@ +package step.automation.packages.yaml; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import step.automation.packages.yaml.deserialization.PatchingParserDelegate; + + +public class LocationAwareTreeTraversingParser extends PatchingParserDelegate { + + private final LocatedJsonNode sourceNode; + + public LocationAwareTreeTraversingParser(JsonParser delegate, + LocatedJsonNode sourceNode) { + super(delegate); + this.sourceNode = sourceNode; + } + + @Override + public JsonLocation currentLocation() { + // return the captured location instead of the default one + // which would just point to the original source + return sourceNode.getStartLocation(); + } + + public LocatedJsonNode getSourceNode() { + return sourceNode; + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java new file mode 100644 index 0000000000..18bf07b513 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java @@ -0,0 +1,55 @@ +package step.automation.packages.yaml.deserialization; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import step.core.yaml.PatchableYamlList; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +public class PatchableYamlListDeserializer extends CollectionDeserializer { + + private final CollectionDeserializer delegate; + + public PatchableYamlListDeserializer(CollectionDeserializer delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public Collection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + if (p instanceof PatchingParserDelegate) { + PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; + JsonLocation startFieldLocation = patchingParser.getDistinctLocationBeforeToken(JsonToken.FIELD_NAME); + JsonLocation startLocation = patchingParser.getDistinctLocationBeforeToken(JsonToken.START_ARRAY); + Collection entity = super.deserialize(p, ctxt); + PatchableYamlList patchableYamlList = new PatchableYamlList<>(entity); + patchableYamlList.setPatchingBounds(startLocation, (int) startFieldLocation.getCharOffset(), patchingParser.currentLocation()); + return patchableYamlList; + } + return super.deserialize(p, ctxt); + } + + + @Override + protected CollectionDeserializer withResolved( + JsonDeserializer keyDeser, + JsonDeserializer valueDeser, + TypeDeserializer valueTypeDeser, + NullValueProvider nuller, + Boolean unwrapSingle) { + CollectionDeserializer resolved = super.withResolved(keyDeser, valueDeser, valueTypeDeser, nuller, unwrapSingle); + // Return your custom instance instead of the default one + return new PatchableYamlListDeserializer(resolved); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java similarity index 82% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java rename to step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java index 8502f9da59..87bd364bf5 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlArtefactDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java @@ -9,15 +9,15 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; -import step.plans.parser.yaml.PatchableYamlArtefact; +import step.core.yaml.PatchableYamlModel; import java.io.IOException; -public class PatchableYamlArtefactDeserializer extends JsonDeserializer implements ContextualDeserializer { +public class PatchableYamlModelDeserializer extends JsonDeserializer implements ContextualDeserializer { private final JsonDeserializer delegate; - public PatchableYamlArtefactDeserializer(JsonDeserializer delegate) { + public PatchableYamlModelDeserializer(JsonDeserializer delegate) { this.delegate = (JsonDeserializer) delegate; } @@ -34,7 +34,7 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOExcepti } JsonLocation startItem = patchingParser.currentLocation(); T entity = delegate.deserialize(p, ctxt); - entity.setPatchingBounds(startItem, startList, startListItemOffset, patchingParser.getLastDistinctLocation()); + entity.setPatchingBounds(startItem, startListItemOffset, patchingParser.getLastDistinctLocation()); return entity; } @@ -52,6 +52,6 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (contextual instanceof ResolvableDeserializer) { ((ResolvableDeserializer) contextual).resolve(ctxt); } - return new PatchableYamlArtefactDeserializer<>((JsonDeserializer) contextual); + return new PatchableYamlModelDeserializer<>((JsonDeserializer) contextual); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java index e01d3eecca..ba9849608e 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java @@ -4,8 +4,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.util.JsonParserDelegate; +import com.fasterxml.jackson.databind.node.ObjectNode; +import step.automation.packages.yaml.LocatedJsonNode; import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; import java.util.Map; @@ -15,18 +19,26 @@ public class PatchingParserDelegate extends JsonParserDelegate { private JsonLocation lastDistinctLocation; + private final Deque nodeStack = new ArrayDeque<>(); + public PatchingParserDelegate(JsonParser d) { super(d); } @Override public JsonToken nextToken() throws IOException { - JsonLocation preLocation = super.currentLocation(); + JsonLocation preLocation = currentLocation(); JsonToken token = super.nextToken(); - if (!preLocation.equals(super.currentLocation())) { + if (!preLocation.equals(currentLocation())) { lastDistinctLocation = preLocation; } distinctLocationBeforeToken.put(token, lastDistinctLocation); + + if (token == JsonToken.END_OBJECT && !nodeStack.isEmpty()) { + LocatedJsonNode currentNode = nodeStack.pop(); + currentNode.setEndLocation(currentLocation()); + } + return token; } @@ -37,4 +49,10 @@ protected JsonLocation getDistinctLocationBeforeToken(JsonToken token) { protected JsonLocation getLastDistinctLocation() { return lastDistinctLocation; } + + public ObjectNode setCurrentObjectNode(LocatedJsonNode jsonNode) { + jsonNode.setStartLocation(currentLocation()); + nodeStack.add(jsonNode); + return jsonNode; + } } 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 dd45701bf9..caeb75c933 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 @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.YamlAutomationPackageKeyword; +import step.core.yaml.PatchableYamlList; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; @@ -35,7 +36,7 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio private final AutomationPackageSerializationRegistry serializationRegistry; private List fragments = new ArrayList<>(); private List keywords = new ArrayList<>(); - private List plans = new ArrayList<>(); + private PatchableYamlList plans = new PatchableYamlList<>(); private List plansPlainText = new ArrayList<>(); private final Map> additionalFields = new HashMap<>(); @@ -45,7 +46,7 @@ public AbstractAutomationPackageFragmentYaml(@JacksonInject(useInput = OptBoolea this.mapper = mapper; this.serializationRegistry = serializationRegistry; } - + public AbstractAutomationPackageFragmentYaml() { this.mapper = null; this.serializationRegistry = null; @@ -53,7 +54,7 @@ public AbstractAutomationPackageFragmentYaml() { @JsonIgnore private URL url; - + @JsonIgnore private String currentYaml; @@ -63,17 +64,17 @@ public List getKeywords() { } @JsonSetter(nulls = Nulls.AS_EMPTY) - public void setKeywords(List keywords) { + public void setKeywords(PatchableYamlList keywords) { this.keywords = keywords; } @Override - public List getPlans() { + public PatchableYamlList getPlans() { return plans; } @JsonSetter(nulls = Nulls.AS_EMPTY) - public void setPlans(List plans) { + public void setPlans(PatchableYamlList plans) { this.plans = plans; } @@ -96,7 +97,7 @@ public Map> getAdditionalFields() { @Override public void setAdditionalFields(String key, JsonNode node) throws IOException { if (mapper == null || serializationRegistry == null) return; - + // acquire reader for the right type Class targetClass = serializationRegistry.resolveClassForYamlField(key); if (targetClass == null) return; 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 14db5bf624..f63c798441 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 @@ -18,11 +18,14 @@ ******************************************************************************/ package step.automation.packages.yaml.model; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import step.automation.packages.model.YamlAutomationPackageKeyword; +import step.core.yaml.PatchableYamlList; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; +import javax.json.Json; import java.io.IOException; import java.net.URL; import java.util.List; @@ -32,7 +35,7 @@ public interface AutomationPackageFragmentYaml { List getKeywords(); - List getPlans(); + PatchableYamlList getPlans(); List getPlansPlainText(); @@ -43,14 +46,14 @@ public interface AutomationPackageFragmentYaml { default List getAdditionalField(String k) { return (List) getAdditionalFields().get(k); } - + void setAdditionalFields(String key, JsonNode value) throws IOException; URL getFragmentUrl(); void setFragmentUrl(URL url); - + String getCurrentYaml(); - + void setCurrentYaml(String yaml); } diff --git a/step-core-model/src/main/java/step/core/yaml/AbstractYamlModel.java b/step-core-model/src/main/java/step/core/yaml/AbstractYamlModel.java index 30eb698863..1da8fa469a 100644 --- a/step-core-model/src/main/java/step/core/yaml/AbstractYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/AbstractYamlModel.java @@ -19,6 +19,7 @@ package step.core.yaml; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonLocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import step.core.ReflectionUtils; @@ -30,6 +31,7 @@ public class AbstractYamlModel { private static final Logger log = LoggerFactory.getLogger(AbstractYamlModel.class); + protected void copyFieldsToObject(Object to, boolean ignoreNulls) { List allFieldsYaml = getAutoCopyFields(); List allFieldsTo = ReflectionUtils.getAllFieldsInHierarchy(to.getClass(), null); diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java b/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java new file mode 100644 index 0000000000..473ab1f3b6 --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (C) 2020, 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.yaml; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +public class PatchableAbstractYamlModel extends AbstractYamlModel implements PatchableYamlModel { + + @JsonIgnore + private int startOffset = -1; + + @JsonIgnore + private int startFieldOffset = -1; + + @JsonIgnore + private int startColumn = -1; + + @JsonIgnore + private int endOffset = -1; + + @JsonIgnore + public void setPatchingBounds(JsonLocation startLocation, int startFieldLocation, JsonLocation endLocation) { + startFieldOffset = startFieldLocation; + startOffset = (int) startLocation.getCharOffset(); + endOffset = (int) endLocation.getCharOffset(); + startColumn = startLocation.getColumnNr() -1; + } + + @JsonIgnore + public int getStartOffset(){ + return startOffset; + } + + @JsonIgnore + public int getIndent() { + return startColumn; + } + + @JsonIgnore + public int getEndOffset() { + return endOffset; + } + + @JsonIgnore + public void setPatchingBounds(PatchableYamlModel newBoundedArtefact) { + startFieldOffset = newBoundedArtefact.getStartFieldOffset(); + startOffset = newBoundedArtefact.getStartOffset(); + startColumn = newBoundedArtefact.getIndent(); + endOffset = newBoundedArtefact.getEndOffset(); + } + + @Override + public int getStartFieldOffset() { + return startFieldOffset; + } + +} diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java new file mode 100644 index 0000000000..a1f167da8c --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (C) 2020, 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.yaml; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class PatchableYamlList extends ArrayList implements PatchableYamlModel { + + @JsonIgnore + private int startOffset = -1; + + @JsonIgnore + private int startColumn = -1; + + @JsonIgnore + private int endOffset = -1; + + @JsonIgnore + private int startFieldOffset; + + public PatchableYamlList() { + super(); + } + + public PatchableYamlList(Collection delegate) { + super(delegate); + } + + @JsonIgnore + public void setPatchingBounds(JsonLocation startLocation, int startFieldLocation, JsonLocation endLocation) { + startFieldOffset = startFieldLocation; + startOffset = (int) startLocation.getCharOffset(); + endOffset = (int) endLocation.getCharOffset(); + startColumn = startLocation.getColumnNr() -1; + } + + @JsonIgnore + public int getStartOffset(){ + return startOffset; + } + + @JsonIgnore + public int getIndent() { + return startColumn; + } + + @JsonIgnore + public int getEndOffset() { + return endOffset; + } + + @JsonIgnore + public void setPatchingBounds(PatchableYamlModel newBoundedArtefact) { + startFieldOffset = newBoundedArtefact.getStartFieldOffset(); + startOffset = newBoundedArtefact.getStartOffset(); + startColumn = newBoundedArtefact.getIndent(); + endOffset = newBoundedArtefact.getEndOffset(); + } + + @Override + public int getStartFieldOffset() { + return startFieldOffset; + } +} diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java new file mode 100644 index 0000000000..ee74a3e9cc --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java @@ -0,0 +1,18 @@ +package step.core.yaml; + +import com.fasterxml.jackson.core.JsonLocation; + +public interface PatchableYamlModel { + + void setPatchingBounds(JsonLocation startLocation, int startFieldLocation, JsonLocation endLocation); + + int getStartOffset(); + + int getIndent(); + + int getEndOffset(); + + void setPatchingBounds(PatchableYamlModel newBoundedArtefact); + + int getStartFieldOffset(); +} diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java deleted file mode 100644 index ec8acaaae3..0000000000 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/PatchableYamlArtefact.java +++ /dev/null @@ -1,68 +0,0 @@ -package step.plans.parser.yaml; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonLocation; - -public abstract class PatchableYamlArtefact { - - @JsonIgnore - private int startOffset = -1; - - @JsonIgnore - private int startColumn = -1; - - @JsonIgnore - private int endOffset = -1; - - @JsonIgnore - private int startListItemOffset = -1; - - @JsonIgnore - private int startListOffset = -1; - - @JsonIgnore - public void setPatchingBounds(JsonLocation startLocation, JsonLocation startList, int startListItemOffset, JsonLocation endLocation) { - startOffset = (int) startLocation.getCharOffset(); - endOffset = (int) endLocation.getCharOffset(); - startColumn = startLocation.getColumnNr() -1; - this.startListItemOffset =startListItemOffset; - startListOffset = (int) startList.getCharOffset(); - } - - @JsonIgnore - public int getStartOffset(){ - return startOffset; - } - - @JsonIgnore - public int getIndent() { - return startColumn; - } - - @JsonIgnore - public int getEndOffset() { - return endOffset; - } - - - public void setPatchingBounds(PatchableYamlArtefact newBoundedArtefact) { - startOffset = newBoundedArtefact.startOffset; - startColumn = newBoundedArtefact.startColumn; - endOffset = newBoundedArtefact.endOffset; - startListItemOffset = newBoundedArtefact.startListItemOffset; - startListOffset = newBoundedArtefact.startListOffset; - } - - @JsonIgnore - abstract public String getCollectionName(); - - @JsonIgnore - public int getStartListItemOffset() { - return startListItemOffset; - } - - @JsonIgnore - public int getStartListOffset() { - return startListOffset; - } -} diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java index 9c9451052c..e00af699cc 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java @@ -18,6 +18,8 @@ ******************************************************************************/ package step.plans.parser.yaml; +import step.core.yaml.AbstractYamlModel; +import step.core.yaml.PatchableAbstractYamlModel; import step.core.yaml.model.NamedYamlArtefact; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -27,7 +29,7 @@ import java.util.List; -public class YamlPlan extends PatchableYamlArtefact { +public class YamlPlan extends PatchableAbstractYamlModel { public static final String PLANS_ENTITY_NAME = "plans"; @@ -84,9 +86,4 @@ public List getCategories() { public void setCategories(List categories) { this.categories = categories; } - - @Override - public String getCollectionName() { - return PLANS_ENTITY_NAME; - } } diff --git a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java index efa792f003..6dde45e740 100644 --- a/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java +++ b/step-plans/step-plans-parser/src/main/java/step/plans/automation/YamlPlainTextPlan.java @@ -18,12 +18,14 @@ ******************************************************************************/ package step.plans.automation; +import step.core.yaml.AbstractYamlModel; +import step.core.yaml.PatchableAbstractYamlModel; import step.plans.nl.RootArtefactType; -import step.plans.parser.yaml.PatchableYamlArtefact; +import step.core.yaml.PatchableYamlModel; import java.util.List; -public class YamlPlainTextPlan extends PatchableYamlArtefact { +public class YamlPlainTextPlan extends PatchableAbstractYamlModel { private String name; @@ -64,9 +66,4 @@ public RootArtefactType getRootType() { public void setRootType(RootArtefactType rootType) { this.rootType = rootType; } - - @Override - public String getCollectionName() { - return AutomationPackagePlainTextPlanJsonSchema.FIELD_NAME_IN_AP; - } } From 9db55abc0415c06754dc51e3d589de129e10f174 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 20 Mar 2026 16:36:01 +0100 Subject: [PATCH 15/15] SED-4539 Define Collections as Patchable --- .../step/automation/packages/yaml/LocatedJsonNode.java | 4 ---- .../packages/yaml/LocationAwareTreeTraversingParser.java | 6 ------ .../deserialization/PatchableYamlListDeserializer.java | 8 +------- .../deserialization/PatchableYamlModelDeserializer.java | 5 ++--- .../yaml/deserialization/PatchingParserDelegate.java | 2 +- .../yaml/model/AbstractAutomationPackageFragmentYaml.java | 2 +- .../src/main/java/step/core/yaml/PatchableYamlList.java | 7 ------- .../src/main/java/step/plans/parser/yaml/YamlPlan.java | 1 - 8 files changed, 5 insertions(+), 30 deletions(-) diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java index 23edd99ef8..78b600cc6c 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java @@ -29,10 +29,6 @@ public JsonParser traverse() { return new LocationAwareTreeTraversingParser(super.traverse(), this); } - public JsonLocation getEndLocation() { - return endLocation; - } - public JsonLocation getStartLocation() { return startLocation; } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java index b7eedf8aa4..f2c0648325 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java @@ -17,12 +17,6 @@ public LocationAwareTreeTraversingParser(JsonParser delegate, @Override public JsonLocation currentLocation() { - // return the captured location instead of the default one - // which would just point to the original source return sourceNode.getStartLocation(); } - - public LocatedJsonNode getSourceNode() { - return sourceNode; - } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java index 18bf07b513..790ce21f2e 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java @@ -6,8 +6,6 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.KeyDeserializer; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.NullValueProvider; import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; @@ -15,19 +13,15 @@ import java.io.IOException; import java.util.Collection; -import java.util.List; public class PatchableYamlListDeserializer extends CollectionDeserializer { - private final CollectionDeserializer delegate; - public PatchableYamlListDeserializer(CollectionDeserializer delegate) { super(delegate); - this.delegate = delegate; } @Override - public Collection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public Collection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (p instanceof PatchingParserDelegate) { PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; JsonLocation startFieldLocation = patchingParser.getDistinctLocationBeforeToken(JsonToken.FIELD_NAME); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java index 87bd364bf5..615816b748 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java @@ -25,8 +25,7 @@ public PatchableYamlModelDeserializer(JsonDeserializer delegate) { public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (p instanceof PatchingParserDelegate) { PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; - JsonLocation startList = patchingParser.getDistinctLocationBeforeToken(JsonToken.FIELD_NAME); - int startListItemOffset = 0; + int startListItemOffset; if (p.getLastClearedToken() == JsonToken.END_OBJECT) { startListItemOffset = (int) patchingParser.getDistinctLocationBeforeToken(JsonToken.END_OBJECT).getCharOffset(); } else { @@ -52,6 +51,6 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (contextual instanceof ResolvableDeserializer) { ((ResolvableDeserializer) contextual).resolve(ctxt); } - return new PatchableYamlModelDeserializer<>((JsonDeserializer) contextual); + return new PatchableYamlModelDeserializer<>(contextual); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java index ba9849608e..3b6f1951c8 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java @@ -15,7 +15,7 @@ public class PatchingParserDelegate extends JsonParserDelegate { - private Map distinctLocationBeforeToken = new HashMap<>(); + private final Map distinctLocationBeforeToken = new HashMap<>(); private JsonLocation lastDistinctLocation; 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 caeb75c933..97b06e71be 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 @@ -50,7 +50,7 @@ public AbstractAutomationPackageFragmentYaml(@JacksonInject(useInput = OptBoolea public AbstractAutomationPackageFragmentYaml() { this.mapper = null; this.serializationRegistry = null; - }; + } @JsonIgnore private URL url; diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java index a1f167da8c..99cbb3356b 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java @@ -20,15 +20,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonLocation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import step.core.ReflectionUtils; - -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; public class PatchableYamlList extends ArrayList implements PatchableYamlModel { diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java index e00af699cc..1dfe98892f 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java @@ -18,7 +18,6 @@ ******************************************************************************/ package step.plans.parser.yaml; -import step.core.yaml.AbstractYamlModel; import step.core.yaml.PatchableAbstractYamlModel; import step.core.yaml.model.NamedYamlArtefact; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;