From 58e52f02d0bde1bed1a558563bed57599262e854 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 6 Feb 2026 11:09:06 +0100 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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; From e3200c3e49a3ec8848fbe8e777a8e979cdae7197 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Fri, 10 Apr 2026 12:26:29 +0200 Subject: [PATCH 16/30] SED-4539 WIP Refactor --- .../AutomationPackageCollectionTest.java | 3 +- .../schema/YamlKeywordSchemaGenerator.java | 2 +- .../AutomationPackageDescriptorReader.java | 48 ++--- ...AutomationPackageWriteToDiskException.java | 2 + .../AutomationPackageYamlFragmentManager.java | 125 ++----------- .../YamlKeywordDeserializer.java | 30 +-- ...AbstractAutomationPackageFragmentYaml.java | 58 ++++-- .../AutomationPackageDescriptorYamlImpl.java | 16 +- .../model/AutomationPackageFragmentYaml.java | 11 +- .../AutomationPackageFragmentYamlImpl.java | 15 +- step-core-model/pom.xml | 5 + .../core/yaml/PatchableAbstractYamlModel.java | 57 +++--- .../step/core/yaml/PatchableYamlList.java | 83 --------- .../step/core/yaml/PatchableYamlModel.java | 11 +- ...omationPackageConcurrentEditException.java | 2 +- .../AutomationPackageUpdateException.java | 2 +- .../deserialization}/LocatedJsonNode.java | 12 +- .../LocatedYamlObjectFactory.java | 5 +- .../LocationAwareTreeTraversingParser.java | 6 +- .../deserialization/PatchableYamlList.java | 176 ++++++++++++++++++ .../PatchableYamlListDeserializer.java | 19 +- .../PatchableYamlModelDeserializer.java | 11 +- .../yaml/deserialization/PatchingContext.java | 147 +++++++++++++++ .../PatchingParserDelegate.java | 23 ++- .../model/YamlAutomationPackageKeyword.java | 13 ++ .../AutomationPackageKeywordsLookuper.java | 3 +- .../java/step/plans/parser/yaml/YamlPlan.java | 9 + .../plans/automation/YamlPlainTextPlan.java | 9 + .../plans/parser/yaml/YamlPlanReader.java | 48 ++++- .../UpgradableYamlPlanDeserializer.java | 29 ++- 30 files changed, 615 insertions(+), 365 deletions(-) delete mode 100644 step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml => step-core-model/src/main/java/step/core/yaml/deserialization}/AutomationPackageConcurrentEditException.java (82%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml => step-core-model/src/main/java/step/core/yaml/deserialization}/AutomationPackageUpdateException.java (86%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml => step-core-model/src/main/java/step/core/yaml/deserialization}/LocatedJsonNode.java (78%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml => step-core-model/src/main/java/step/core/yaml/deserialization}/LocatedYamlObjectFactory.java (78%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml => step-core-model/src/main/java/step/core/yaml/deserialization}/LocationAwareTreeTraversingParser.java (76%) create mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages => step-core-model/src/main/java/step/core}/yaml/deserialization/PatchableYamlListDeserializer.java (69%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages => step-core-model/src/main/java/step/core}/yaml/deserialization/PatchableYamlModelDeserializer.java (78%) create mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages => step-core-model/src/main/java/step/core}/yaml/deserialization/PatchingParserDelegate.java (68%) rename {step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages => step-core/src/main/java/step/core}/yaml/AutomationPackageKeywordsLookuper.java (96%) 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 df3a96df65..e2cdbf29b7 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 @@ -30,7 +30,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.core.yaml.deserialization.AutomationPackageConcurrentEditException; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.automation.packages.yaml.YamlAutomationPackageVersions; import step.core.dynamicbeans.DynamicValue; @@ -291,7 +291,6 @@ 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-schema/src/main/java/step/automation/packages/yaml/schema/YamlKeywordSchemaGenerator.java b/step-automation-packages/step-automation-packages-schema/src/main/java/step/automation/packages/yaml/schema/YamlKeywordSchemaGenerator.java index 37e234c68a..cb234eb386 100644 --- a/step-automation-packages/step-automation-packages-schema/src/main/java/step/automation/packages/yaml/schema/YamlKeywordSchemaGenerator.java +++ b/step-automation-packages/step-automation-packages-schema/src/main/java/step/automation/packages/yaml/schema/YamlKeywordSchemaGenerator.java @@ -23,7 +23,7 @@ import jakarta.json.spi.JsonProvider; import step.core.yaml.YamlModelUtils; import step.automation.packages.model.AbstractYamlFunction; -import step.automation.packages.yaml.AutomationPackageKeywordsLookuper; +import step.core.yaml.AutomationPackageKeywordsLookuper; import step.core.scanner.CachedAnnotationScanner; import step.core.yaml.schema.AggregatedJsonSchemaFieldProcessor; import step.core.yaml.schema.JsonSchemaDefinitionAddOn; 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 6c84eb6c7b..cedac837fc 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 @@ -32,16 +32,16 @@ import step.artefacts.handlers.JsonSchemaValidator; import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -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.core.yaml.PatchableYamlModel; +import step.core.yaml.deserialization.*; +import step.plans.parser.yaml.YamlPlan; import step.plans.parser.yaml.YamlPlanReader; +import step.plans.parser.yaml.deserializers.UpgradableYamlPlanDeserializer; import step.plans.parser.yaml.model.YamlPlanVersions; import step.plans.parser.yaml.schema.YamlPlanValidationException; @@ -109,11 +109,16 @@ protected T readAutomationPackageYamlF throw new YamlPlanValidationException(message, ex); } } - - PatchingParserDelegate parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString)); + PatchingContext context = new PatchingContext(yamlDescriptorString, yamlObjectMapper); + PatchingParserDelegate parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString), context); yamlObjectMapper.setNodeFactory(new LocatedYamlObjectFactory(parser)); + yamlObjectMapper.setInjectableValues(new InjectableValues.Std() + .addValue(ObjectMapper.class, yamlObjectMapper) + .addValue(AutomationPackageSerializationRegistry.class, serializationRegistry) + .addValue(PatchingContext.class, context) + ); T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(parser, targetClass); - res.setCurrentYaml(yamlDescriptorString); + res.setPatchingContext(context); logAfterRead(packageName, res); return res; } catch (IOException | YamlPlanValidationException e) { @@ -156,39 +161,10 @@ 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) - ); - // configure custom deserializers - 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 (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; - } - }); - } - }; // register deserializers to read yaml plans - planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); + SimpleModule module = planReader.registerAllSerializersAndDeserializers(yamlMapper, true); yamlMapper.registerModule(module); return yamlMapper; 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 index 5ebf9deee5..6a3c277482 100644 --- 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 @@ -1,5 +1,7 @@ package step.automation.packages.yaml; +import step.core.yaml.deserialization.AutomationPackageUpdateException; + 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 15c0fe9c49..5b6caad742 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,18 +18,16 @@ ******************************************************************************/ 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.core.yaml.deserialization.AutomationPackageUpdateException; +import step.core.yaml.deserialization.PatchableYamlList; 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.core.yaml.PatchableYamlList; import step.core.yaml.PatchableYamlModel; +import step.core.yaml.deserialization.PatchingContext; import step.plans.parser.yaml.YamlPlan; import java.io.*; @@ -92,7 +90,7 @@ public Plan savePlan(Plan p) { fragment = fragmentForNewPlan(p); planToYamlFragment.put(p, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - addFragmentEntity(fragment, YamlPlan.PLANS_ENTITY_NAME, fragment.getPlans(), newYamlPlan); + addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { YamlPlan yamlPlan = planToYamlPlan.get(p); modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); @@ -102,70 +100,14 @@ public Plan savePlan(Plan p) { return p; } - 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.getStartFieldOffset(), lastEntity.getStartOffset()); - String indent = " ".repeat(lastEntity.getIndent()); - String entityYaml = entityStringWithIndent(indent, 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); - - if (yaml == null) { - return "---\n" + listYaml; - } else { - return yaml.substring(0, entityList.getStartFieldOffset()) - + listYaml + yaml.substring(entityList.getEndOffset()); - } - - }); - } + private void addFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T newEntity) { + entityList.add(newEntity); + fragment.writeToDisk(); } 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(); - String indentString = " ".repeat(indent); - String newArtefactString = entityStringWithIndent(indentString, newEntity); - return yaml.substring(0, oldEntity.getStartOffset()) - + newArtefactString + yaml.substring(oldEntity.getEndOffset()); - - }); - } - - 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, PatchableYamlList entityList, T entity) { - entityList.remove(entity); - writeFragmentToDisk(fragment, yaml -> { - int s = entityList.isEmpty() ? entityList.getStartFieldOffset() : entity.getStartFieldOffset(); - return yaml.substring(0, s) + yaml.substring(entity.getEndOffset()); - }); + entityList.replaceItem(oldEntity, newEntity); + fragment.writeToDisk(); } private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { @@ -185,7 +127,8 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { if (pathToYamlFragment.containsKey(url.toString())) return pathToYamlFragment.get(url.toString()); - AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(); + PatchingContext context = new PatchingContext("---", descriptorYaml.getPatchingContext().getMapper()); + AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); fragment.setFragmentUrl(url); return fragment; } catch (MalformedURLException e) { @@ -207,50 +150,6 @@ public void removePlan(Plan p) { planToYamlPlan.remove(p); planToYamlFragment.remove(p); - removeFragmentEntity(fragment, fragment.getPlans(), yamlPlan); - } - - private void updateFragmentObjectOffsets(AutomationPackageFragmentYaml fragment) { - try { - 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 (IOException e) { - throw new AutomationPackageUpdateException("Error re-writing automation package fragment {0}", e); - } - } - - private void updateFragmentObjectOffsets(PatchableYamlList newOffsetEntities, PatchableYamlList entities) { - Iterator newIt = newOffsetEntities.iterator(); - Iterator it = entities.iterator(); - - 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."); - } - - entities.setPatchingBounds(newOffsetEntities); - } - - 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 AutomationPackageWriteToDiskException(MessageFormat.format("Error when writing automation package fragment {0} back to disk.", fragment.getFragmentUrl()), e); - } + fragment.writeToDisk(); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java index 540ade334f..83633eb656 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.model.AbstractYamlFunction; import step.automation.packages.model.YamlAutomationPackageKeyword; -import step.automation.packages.yaml.AutomationPackageKeywordsLookuper; +import step.core.yaml.AutomationPackageKeywordsLookuper; import step.core.yaml.deserializers.NamedEntityYamlDeserializer; import step.core.yaml.deserializers.StepYamlDeserializer; import step.core.yaml.deserializers.StepYamlDeserializerAddOn; @@ -47,26 +47,14 @@ public YamlKeywordDeserializer(ObjectMapper yamlObjectMapper) { @Override public YamlAutomationPackageKeyword deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - NamedEntityYamlDeserializer> nameEntityDeserializer = new NamedEntityYamlDeserializer<>() { - @Override - protected String resolveTargetClassNameByYamlName(String yamlName) { - return null; - } - - protected Class resolveTargetClassByYamlName(String yamlName) { - try { - String className = keywordsLookuper.yamlKeywordClassToJava(yamlName); - if (className == null) { - throw new RuntimeException("Unable to resolve keyword class for '" + yamlName + "'"); - } - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Unable to resolve keyword class for '" + yamlName + "'"); - } - } - }; - return new YamlAutomationPackageKeyword(nameEntityDeserializer.deserialize(node, jsonParser.getCodec())); + String yamlName = jsonParser.nextFieldName(); + + try { + Class clazz = Class.forName(keywordsLookuper.yamlKeywordClassToJava(yamlName)); + return new YamlAutomationPackageKeyword((AbstractYamlFunction) deserializationContext.readValue(jsonParser, clazz)); + } catch (ClassNotFoundException 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 97b06e71be..f1188629a7 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 @@ -21,43 +21,47 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.FileUtils; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.YamlAutomationPackageKeyword; -import step.core.yaml.PatchableYamlList; +import step.automation.packages.yaml.AutomationPackageWriteToDiskException; +import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; +import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchingContext; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; +import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; import java.util.*; +@JsonInclude(JsonInclude.Include.NON_EMPTY) 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 PatchableYamlList plans = new PatchableYamlList<>(); + private PatchableYamlList plans; private List plansPlainText = new ArrayList<>(); private final Map> additionalFields = new HashMap<>(); + private PatchingContext context; + private long fileLastModified = 0; - @JsonCreator - public AbstractAutomationPackageFragmentYaml(@JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry) { + public AbstractAutomationPackageFragmentYaml(ObjectMapper mapper, AutomationPackageSerializationRegistry serializationRegistry, PatchingContext patchingContext) { this.mapper = mapper; this.serializationRegistry = serializationRegistry; - } - - public AbstractAutomationPackageFragmentYaml() { - this.mapper = null; - this.serializationRegistry = null; + context = patchingContext; + plans = new PatchableYamlList<>(patchingContext, YamlPlan.PLANS_ENTITY_NAME); } @JsonIgnore private URL url; - @JsonIgnore - private String currentYaml; - @Override public List getKeywords() { return keywords; @@ -118,21 +122,43 @@ public void setPlansPlainText(List plansPlainText) { @JsonIgnore public void setFragmentUrl(URL url) { + resetLastModified(); this.url = url; } + private void resetLastModified() { + fileLastModified = System.currentTimeMillis(); + } + @JsonIgnore public URL getFragmentUrl() { return url; } @JsonIgnore - public void setCurrentYaml(String yaml) { - this.currentYaml = yaml; + @Override + public void setPatchingContext(PatchingContext context) { + this.context = context; } @JsonIgnore - public String getCurrentYaml() { - return currentYaml; + @Override + public PatchingContext getPatchingContext() { + return context; + } + + + @Override + public void writeToDisk() { + try { + File file = new File(url.toURI()); + if (file.exists() && file.lastModified() > fileLastModified) { + throw new AutomationPackageConcurrentEditException(MessageFormat.format("Automation package fragment {0} was edited outside the editor.", url)); + } + FileUtils.writeStringToFile(file, context.getYaml(), StandardCharsets.UTF_8); + resetLastModified(); + } catch (IOException | URISyntaxException e) { + throw new AutomationPackageWriteToDiskException(MessageFormat.format("Error when writing automation package fragment {0} back to disk.", url), e); + } } } 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 a39b1f51db..fc0c965c2f 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 @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.OptBoolean; import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.core.yaml.deserialization.PatchingContext; import java.util.HashMap; import java.util.Map; @@ -36,12 +37,16 @@ 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( + @JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, + @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry, + @JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext + ) { + super(mapper, serializationRegistry, patchingContext); } - - public AutomationPackageDescriptorYamlImpl() { - super(); + + public AutomationPackageDescriptorYamlImpl(PatchingContext context) { + this(null, null, context); } @Override @@ -70,4 +75,5 @@ public Map getAttributes() { public void setAttributes(Map attributes) { this.attributes = attributes; } + } 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 f63c798441..3eb90d041e 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,14 +18,13 @@ ******************************************************************************/ 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.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchingContext; 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; @@ -53,7 +52,9 @@ default List getAdditionalField(String k) { void setFragmentUrl(URL url); - String getCurrentYaml(); + PatchingContext getPatchingContext(); - void setCurrentYaml(String yaml); + void setPatchingContext(PatchingContext context); + + void writeToDisk(); } 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 3ec0ac228f..6ffe81122e 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 @@ -23,13 +23,20 @@ import com.fasterxml.jackson.annotation.OptBoolean; import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.core.yaml.deserialization.PatchingContext; 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( + @JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, + @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry, + @JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext + ) { + super(mapper, serializationRegistry, patchingContext); + } + + public AutomationPackageFragmentYamlImpl(PatchingContext context) { + this(null, null, context); } - - public AutomationPackageFragmentYamlImpl() { super();} } diff --git a/step-core-model/pom.xml b/step-core-model/pom.xml index ddef4e34fe..b2b6646b2d 100644 --- a/step-core-model/pom.xml +++ b/step-core-model/pom.xml @@ -43,6 +43,11 @@ ch.exense.step step-framework-model + + ch.exense.step + step-functions-plugins-java-keyword-handler + 0.0.0-SNAPSHOT + 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 index 473ab1f3b6..3fe7cf9467 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java @@ -20,34 +20,32 @@ 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; +import com.fasterxml.jackson.databind.ObjectMapper; +import step.core.yaml.deserialization.PatchingContext; public class PatchableAbstractYamlModel extends AbstractYamlModel implements PatchableYamlModel { - @JsonIgnore - private int startOffset = -1; + private PatchingContext context; @JsonIgnore - private int startFieldOffset = -1; + private int startOffset = -1; @JsonIgnore - private int startColumn = -1; + private int indent = -1; @JsonIgnore private int endOffset = -1; + public PatchableAbstractYamlModel(PatchingContext context) { + this.context = context; + } + @JsonIgnore - public void setPatchingBounds(JsonLocation startLocation, int startFieldLocation, JsonLocation endLocation) { - startFieldOffset = startFieldLocation; + public void setPatchingBounds(JsonLocation startLocation, JsonLocation endLocation) { startOffset = (int) startLocation.getCharOffset(); - endOffset = (int) endLocation.getCharOffset(); - startColumn = startLocation.getColumnNr() -1; + endOffset = context.ensureNextEndOfLineOffset((int) endLocation.getCharOffset()); + indent = startLocation.getColumnNr() -1; + context.getPatchables().add(this); } @JsonIgnore @@ -57,7 +55,7 @@ public int getStartOffset(){ @JsonIgnore public int getIndent() { - return startColumn; + return indent; } @JsonIgnore @@ -65,17 +63,28 @@ 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 void setStartOffset(int startOffset) { + this.startOffset = startOffset; + } + + @Override + public void setEndOffset(int endOffset) { + this.endOffset = endOffset; } + + @JsonIgnore @Override - public int getStartFieldOffset() { - return startFieldOffset; + public void setIndent(int indent) { + this.indent = indent; } + + + @JsonIgnore + @Override + public void setContext(PatchingContext context) { + this.context = context; + } } 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 deleted file mode 100644 index 99cbb3356b..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java +++ /dev/null @@ -1,83 +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.core.yaml; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonLocation; -import java.util.ArrayList; -import java.util.Collection; - -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 index ee74a3e9cc..446db56fa8 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java @@ -1,10 +1,11 @@ package step.core.yaml; import com.fasterxml.jackson.core.JsonLocation; +import step.core.yaml.deserialization.PatchingContext; public interface PatchableYamlModel { - void setPatchingBounds(JsonLocation startLocation, int startFieldLocation, JsonLocation endLocation); + void setPatchingBounds(JsonLocation startLocation, JsonLocation endLocation); int getStartOffset(); @@ -12,7 +13,11 @@ public interface PatchableYamlModel { int getEndOffset(); - void setPatchingBounds(PatchableYamlModel newBoundedArtefact); + void setStartOffset(int startOffset); - int getStartFieldOffset(); + void setEndOffset(int endOffset); + + void setIndent(int indent); + + void setContext(PatchingContext context); } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java similarity index 82% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java index 5f9feb513e..be13001533 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageConcurrentEditException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java @@ -1,4 +1,4 @@ -package step.automation.packages.yaml; +package step.core.yaml.deserialization; public class AutomationPackageConcurrentEditException extends AutomationPackageUpdateException { public AutomationPackageConcurrentEditException(String s) { diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java similarity index 86% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java index 9024882c50..dcf496a132 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageUpdateException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java @@ -1,4 +1,4 @@ -package step.automation.packages.yaml; +package step.core.yaml.deserialization; public class AutomationPackageUpdateException extends RuntimeException { public AutomationPackageUpdateException(String s, Exception e) { diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java similarity index 78% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java index 78b600cc6c..16d375777a 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java @@ -1,21 +1,21 @@ -package step.automation.packages.yaml; +package step.core.yaml.deserialization; 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 final PatchingContext patchingContext; private JsonLocation startLocation; private JsonLocation endLocation; - public LocatedJsonNode(JsonNodeFactory nodeFactory) { + public LocatedJsonNode(JsonNodeFactory nodeFactory, PatchingContext context) { super(nodeFactory); + this.patchingContext = context; } @Override @@ -40,4 +40,8 @@ public void setEndLocation(JsonLocation endLocation) { public void setStartLocation(JsonLocation startLocation) { this.startLocation = startLocation; } + + public PatchingContext getPatchingContext() { + return patchingContext; + } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java similarity index 78% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java index 5dd759fc2d..a2e44b27bc 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedYamlObjectFactory.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java @@ -1,8 +1,7 @@ -package step.automation.packages.yaml; +package step.core.yaml.deserialization; 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; @@ -14,6 +13,6 @@ public LocatedYamlObjectFactory(PatchingParserDelegate parser) { @Override public ObjectNode objectNode() { - return parser.setCurrentObjectNode(new LocatedJsonNode(this)); + return parser.setCurrentObjectNode(new LocatedJsonNode(this, parser.getPatchingContext())); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java similarity index 76% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java index f2c0648325..5629f4a881 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java @@ -1,8 +1,8 @@ -package step.automation.packages.yaml; +package step.core.yaml.deserialization; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; -import step.automation.packages.yaml.deserialization.PatchingParserDelegate; +import com.fasterxml.jackson.core.util.JsonParserDelegate; public class LocationAwareTreeTraversingParser extends PatchingParserDelegate { @@ -11,7 +11,7 @@ public class LocationAwareTreeTraversingParser extends PatchingParserDelegate { public LocationAwareTreeTraversingParser(JsonParser delegate, LocatedJsonNode sourceNode) { - super(delegate); + super(delegate, sourceNode.getPatchingContext()); this.sourceNode = sourceNode; } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java new file mode 100644 index 0000000000..1bf6ea41fe --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * 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.deserialization; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonLocation; +import step.core.yaml.PatchableYamlModel; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.UnaryOperator; + +public class PatchableYamlList extends ArrayList implements PatchableYamlModel{ + + + private PatchingContext context; + private final String fieldName; + + @JsonIgnore + private int startOffset = -1; + + @JsonIgnore + private int indent = -1; + + @JsonIgnore + private int endOffset = -1; + + public PatchableYamlList(PatchingContext context, String fieldName) { + + this(new ArrayList<>(), context, fieldName); + } + + protected PatchableYamlList(Collection delegate, PatchingContext context, String fieldName) { + super(delegate); + this.context = context; + this.fieldName = fieldName; + } + + @Override + public boolean remove(Object item) { + if (super.remove(item)) { + PatchableYamlModel patchableItem = (PatchableYamlModel) item; + context.removePatchable(patchableItem); + + if (super.isEmpty()) { + context.removePatchable(this); + } + return true; + } + return false; + } + + @Override + public boolean add(T item) { + if (!context.contains(this)) { + context.appendEmptyPatchable(this); + } + PatchableYamlModel patchableItem = (PatchableYamlModel) item; + + if (super.isEmpty()) { + patchableItem.setIndent(getListItemMarker().length()); + context.replaceContainerPatchable(this, patchableItem, fieldName + ":\n" + getListItemMarker()); + } else { + PatchableYamlModel last = (PatchableYamlModel) get(size()-1); + + context.addPatchableAfter(last, patchableItem, getListItemMarker(last)); + } + super.add(item); + return true; + } + + private String getListItemMarker(PatchableYamlModel last) { + String yaml = context.getYaml(); + int listItemMarkerStartOffset = yaml.lastIndexOf("\n", last.getStartOffset()); + return yaml.substring( listItemMarkerStartOffset, last.getStartOffset()); + } + + private String getListItemMarker() { + return " ".repeat(indent) + "- "; + } + + @Override + public void replaceAll(UnaryOperator operator) { + super.replaceAll(item -> { + T newItem = operator.apply(item); + return newItem; + }); + } + + @Override + public boolean addAll(Collection c) { + return c.stream().anyMatch(this::add); + } + + @Override + public boolean removeAll(Collection c) { + return c.stream().anyMatch(this::remove); + } + + @Override + public boolean retainAll(Collection c) { + return removeIf(o -> !c.contains(o)); + } + + @Override + public void clear() { + super.forEach(this::remove); + } + + public void replaceItem(PatchableYamlModel oldEntity, PatchableYamlModel newEntity) { + replaceAll(item -> item == oldEntity ? (T) newEntity : item); + context.replacePatchable(oldEntity, newEntity); + } + + @JsonIgnore + public void setPatchingBounds(JsonLocation startLocation, JsonLocation endLocation) { + startOffset = (int) startLocation.getCharOffset(); + endOffset = context.ensureNextEndOfLineOffset((int) endLocation.getCharOffset()); + indent = startLocation.getColumnNr() -1; + context.getPatchables().add(this); + } + + @Override + public int getStartOffset(){ + return startOffset; + } + + @Override + public int getIndent() { + return indent; + } + + @Override + public int getEndOffset() { + return endOffset; + } + + + @Override + public void setStartOffset(int startOffset) { + this.startOffset = startOffset; + } + + @Override + public void setEndOffset(int endOffset) { + this.endOffset = endOffset; + } + + @JsonIgnore + @Override + public void setIndent(int indent) { + this.indent = indent; + } + + @Override + @JsonIgnore + public void setContext(PatchingContext context) { + this.context = context; + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java similarity index 69% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java index 790ce21f2e..e650266b7e 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java @@ -1,6 +1,5 @@ -package step.automation.packages.yaml.deserialization; +package step.core.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; @@ -9,26 +8,29 @@ 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.ArrayList; import java.util.Collection; 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 { 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()); + JsonLocation startLocation = patchingParser.getLastLocationForToken(JsonToken.FIELD_NAME); + Collection entity = delegate.deserialize(p, ctxt, new ArrayList<>()); + PatchableYamlList patchableYamlList = new PatchableYamlList<>(entity, patchingParser.getPatchingContext(), patchingParser.currentName()); + patchableYamlList.setPatchingBounds(startLocation, patchingParser.getLastDistinctLocation()); return patchableYamlList; } return super.deserialize(p, ctxt); @@ -43,7 +45,6 @@ protected CollectionDeserializer withResolved( 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/PatchableYamlModelDeserializer.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java similarity index 78% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java index 615816b748..bed4f7341a 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java @@ -1,4 +1,4 @@ -package step.automation.packages.yaml.deserialization; +package step.core.yaml.deserialization; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; @@ -25,16 +25,9 @@ public PatchableYamlModelDeserializer(JsonDeserializer delegate) { public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (p instanceof PatchingParserDelegate) { PatchingParserDelegate patchingParser = (PatchingParserDelegate) p; - int startListItemOffset; - 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, startListItemOffset, patchingParser.getLastDistinctLocation()); - + entity.setPatchingBounds(startItem, patchingParser.getLastDistinctLocation()); return entity; } return delegate.deserialize(p, ctxt); diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java new file mode 100644 index 0000000000..7e9e35ce80 --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java @@ -0,0 +1,147 @@ +package step.core.yaml.deserialization; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import step.core.yaml.PatchableYamlModel; + +import java.util.ArrayList; +import java.util.List; + +public class PatchingContext { + private String yaml; + + private final List patchables = new ArrayList<>(); + private final ObjectMapper mapper; + + public PatchingContext() { + yaml = ""; + mapper = new ObjectMapper(); + } + + public PatchingContext(String yaml, ObjectMapper mapper) { + this.yaml = yaml; + this.mapper = mapper; + } + + public String getYaml() { + return yaml; + } + + public ObjectMapper getMapper() { + return mapper; + } + + public List getPatchables() { + return patchables; + } + + public void setYaml(String yaml) { + this.yaml = yaml; + } + + private String entityStringWithIndent(Object entity, int indent) { + try { + String indentString = " ".repeat(indent); + return mapper + .writeValueAsString(entity) + .replaceAll("---\n", "") + .trim() + .replaceAll("\n", "\n" + indentString); + } catch (JsonProcessingException e) { + throw new AutomationPackageUpdateException("Error Serializing new object", e); + } + } + + public void replacePatchable(PatchableYamlModel oldPatchable, PatchableYamlModel newPatchable) { + String entityString = entityStringWithIndent(newPatchable, oldPatchable.getIndent()); + + int endOffset = oldPatchable.getEndOffset(); + int delta = entityString.length() - (endOffset - oldPatchable.getStartOffset()); + + + yaml = yaml.substring(0, oldPatchable.getStartOffset()) + + entityString + + yaml.substring(oldPatchable.getEndOffset()); + + newPatchable.setStartOffset(oldPatchable.getStartOffset()); + newPatchable.setIndent(oldPatchable.getIndent()); + newPatchable.setEndOffset(oldPatchable.getEndOffset() + delta); + + patchables.replaceAll(p -> p == oldPatchable ? newPatchable : p); + + updatePatchableOffsetsAfter(newPatchable, endOffset, delta); + } + + public void removePatchable(PatchableYamlModel patchable) { + if (!patchables.contains(patchable)) return; + + int previousLineEnd = ensurePreviousEndOfLineOffset(patchable.getStartOffset()); + int delta = previousLineEnd - patchable.getEndOffset(); + yaml = yaml.substring(0, previousLineEnd) + yaml.substring(patchable.getEndOffset()); + updatePatchableOffsetsAfter(patchable, patchable.getEndOffset(), delta); + patchables.remove(patchable); + } + + + + public void addPatchableAfter(PatchableYamlModel last, PatchableYamlModel patchableItem, String entityPrefix) { + + String entityString = entityStringWithIndent(patchableItem, last.getIndent()); + + yaml = yaml.substring(0, last.getEndOffset()) + entityPrefix + entityString + yaml.substring(last.getEndOffset()); + + patchableItem.setStartOffset(last.getEndOffset() + entityPrefix.length()); + patchableItem.setEndOffset(last.getEndOffset() + entityPrefix.length() + entityString.length()); + patchableItem.setIndent(last.getIndent()); + patchableItem.setContext(this); + + patchables.add(patchables.indexOf(last)+1, patchableItem); + + updatePatchableOffsetsAfter(patchableItem, patchableItem.getEndOffset(), entityString.length() + entityPrefix.length()); + } + + private void updatePatchableOffsetsAfter(PatchableYamlModel patchable, int endOffset, int delta) { + for(int i = patchables.indexOf(patchable) + 1; i < patchables.size(); i++) { + PatchableYamlModel successor = patchables.get(i); + if (successor.getStartOffset() >= endOffset) { + successor.setStartOffset(successor.getStartOffset()+delta); + } + successor.setEndOffset(successor.getEndOffset()+delta); + } + } + + public boolean contains(PatchableYamlModel patchable) { + return patchables.contains(patchable); + } + + public int ensureNextEndOfLineOffset(int offset) { + return Math.max(yaml.indexOf("\n", offset), offset); + } + + public int ensurePreviousEndOfLineOffset(int offset) { + return Math.max(yaml.lastIndexOf("\n", offset), 0); + } + + public void replaceContainerPatchable(PatchableYamlModel containerPatchable, PatchableYamlModel child, String containerPrefix) { + String childString = entityStringWithIndent(child, child.getIndent()); + yaml = yaml.substring(0, containerPatchable.getStartOffset()) + containerPrefix + childString + yaml.substring(containerPatchable.getEndOffset()); + + containerPatchable.setEndOffset(containerPatchable.getStartOffset() + containerPrefix.length() + childString.length()); + child.setStartOffset(containerPatchable.getStartOffset() + containerPrefix.length()); + child.setEndOffset(containerPatchable.getEndOffset()); + + patchables.add(patchables.indexOf(containerPatchable), child); + + int delta = containerPrefix.length() + childString.length() - (containerPatchable.getEndOffset() - containerPatchable.getStartOffset()); + updatePatchableOffsetsAfter(containerPatchable, containerPatchable.getEndOffset(), delta); + } + + public void appendEmptyPatchable(PatchableYamlModel patchable) { + patchable.setIndent(0); + yaml += "\n"; + patchable.setStartOffset(yaml.length()); + patchable.setEndOffset(yaml.length()); + patchable.setIndent(0); + patchables.add(patchable); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java similarity index 68% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java rename to step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java index 3b6f1951c8..189ed80e0e 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java @@ -1,11 +1,10 @@ -package step.automation.packages.yaml.deserialization; +package step.core.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 com.fasterxml.jackson.databind.node.ObjectNode; -import step.automation.packages.yaml.LocatedJsonNode; import java.io.IOException; import java.util.ArrayDeque; @@ -15,14 +14,18 @@ public class PatchingParserDelegate extends JsonParserDelegate { - private final Map distinctLocationBeforeToken = new HashMap<>(); + private final Map locationForToken = new HashMap<>(); private JsonLocation lastDistinctLocation; private final Deque nodeStack = new ArrayDeque<>(); - public PatchingParserDelegate(JsonParser d) { + protected final PatchingContext patchingContext; + + public PatchingParserDelegate(JsonParser d, PatchingContext context) { + super(d); + patchingContext = context; } @Override @@ -32,18 +35,18 @@ public JsonToken nextToken() throws IOException { if (!preLocation.equals(currentLocation())) { lastDistinctLocation = preLocation; } - distinctLocationBeforeToken.put(token, lastDistinctLocation); + locationForToken.put(token, preLocation); if (token == JsonToken.END_OBJECT && !nodeStack.isEmpty()) { LocatedJsonNode currentNode = nodeStack.pop(); - currentNode.setEndLocation(currentLocation()); + currentNode.setEndLocation(currentTokenLocation()); } return token; } - protected JsonLocation getDistinctLocationBeforeToken(JsonToken token) { - return distinctLocationBeforeToken.get(token); + protected JsonLocation getLastLocationForToken(JsonToken token) { + return locationForToken.get(token); } protected JsonLocation getLastDistinctLocation() { @@ -55,4 +58,8 @@ public ObjectNode setCurrentObjectNode(LocatedJsonNode jsonNode) { nodeStack.add(jsonNode); return jsonNode; } + + protected PatchingContext getPatchingContext() { + return this.patchingContext; + } } diff --git a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java index 7cdbad74a4..36058ddbf4 100644 --- a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java +++ b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java @@ -18,17 +18,29 @@ ******************************************************************************/ package step.automation.packages.model; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.StagingAutomationPackageContext; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.core.yaml.AutomationPackageKeywordsLookuper; import step.functions.Function; +import java.io.IOException; + public class YamlAutomationPackageKeyword implements AutomationPackageKeyword { private AbstractYamlFunction yamlKeyword; + + public YamlAutomationPackageKeyword(AbstractYamlFunction yamlKeyword) { this.yamlKeyword = yamlKeyword; } + public AbstractYamlFunction getYamlKeyword() { return yamlKeyword; } @@ -41,4 +53,5 @@ public void setYamlKeyword(AbstractYamlFunction yamlKeyword) { public Function prepareKeyword(StagingAutomationPackageContext context) { return yamlKeyword.applyAutomationPackageContext(context); } + } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageKeywordsLookuper.java b/step-core/src/main/java/step/core/yaml/AutomationPackageKeywordsLookuper.java similarity index 96% rename from step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageKeywordsLookuper.java rename to step-core/src/main/java/step/core/yaml/AutomationPackageKeywordsLookuper.java index 44a0ebdc46..65cea05448 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageKeywordsLookuper.java +++ b/step-core/src/main/java/step/core/yaml/AutomationPackageKeywordsLookuper.java @@ -16,9 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.automation.packages.yaml; +package step.core.yaml; -import step.core.yaml.YamlModelUtils; import step.automation.packages.model.AbstractYamlFunction; import java.util.List; 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 1dfe98892f..1b11804bc5 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,11 @@ ******************************************************************************/ package step.plans.parser.yaml; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.deserialization.PatchingContext; import step.core.yaml.model.NamedYamlArtefact; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -46,6 +50,11 @@ public class YamlPlan extends PatchableAbstractYamlModel { private List categories; + @JsonCreator + public YamlPlan(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context) { + super(context); + } + public String getName() { return 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 6dde45e740..c695055179 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,8 +18,12 @@ ******************************************************************************/ package step.plans.automation; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; import step.core.yaml.AbstractYamlModel; import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.deserialization.PatchingContext; import step.plans.nl.RootArtefactType; import step.core.yaml.PatchableYamlModel; @@ -35,6 +39,11 @@ public class YamlPlainTextPlan extends PatchableAbstractYamlModel { private String file; + @JsonCreator + public YamlPlainTextPlan(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context) { + super(context); + } + public String getName() { return name; } 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 c0629a811f..e077339ce6 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 @@ -20,8 +20,14 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; +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.bson.types.ObjectId; @@ -36,6 +42,10 @@ import step.core.plans.agents.configuration.AutomaticAgentProvisioningConfiguration; import step.core.scanner.AnnotationScanner; import step.core.scanner.CachedAnnotationScanner; +import step.core.yaml.PatchableYamlModel; +import step.core.yaml.deserialization.PatchableYamlListDeserializer; +import step.core.yaml.deserialization.PatchableYamlModelDeserializer; +import step.core.yaml.deserialization.PatchingContext; import step.core.yaml.deserializers.StepYamlDeserializersScanner; import step.core.yaml.serializers.StepYamlSerializersScanner; import step.migration.MigrationManager; @@ -195,7 +205,7 @@ protected ObjectMapper createYamlPlanObjectMapper() { yamlMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); // configure custom deserializers - yamlMapper.registerModule(registerAllSerializersAndDeserializers(new SimpleModule(), yamlMapper, true)); + yamlMapper.registerModule(registerAllSerializersAndDeserializers(yamlMapper, true)); return yamlMapper; } @@ -212,11 +222,39 @@ private SimpleModule registerBasicSerializersAndDeserializers(SimpleModule modul return res; } - public SimpleModule registerAllSerializersAndDeserializers(SimpleModule module, ObjectMapper resultingMapper, boolean upgradablePlan) { + public SimpleModule registerAllSerializersAndDeserializers(ObjectMapper resultingMapper, boolean upgradablePlan) { ObjectMapper nonUpgradableYamlMapper = createDefaultYamlMapper().registerModule(createModuleForNonUpgradablePlans(resultingMapper)); + // configure custom deserializers + 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 (YamlPlan.class == beanDesc.getBeanClass()) { + deserializer = new UpgradableYamlPlanDeserializer(upgradablePlan ? currentVersion : null, jsonSchema, migrationManager, nonUpgradableYamlMapper, deserializer); + } + + if (PatchableYamlModel.class.isAssignableFrom(beanDesc.getBeanClass()) + && !beanDesc.getBeanClass().equals(PatchableYamlModel.class)) { + return new PatchableYamlModelDeserializer<>(deserializer); + } + return super.modifyDeserializer(config, beanDesc, deserializer); + } - return registerBasicSerializersAndDeserializers(module, resultingMapper) - .addDeserializer(YamlPlan.class, new UpgradableYamlPlanDeserializer(upgradablePlan ? currentVersion : null, jsonSchema, migrationManager, nonUpgradableYamlMapper)); + @Override + public JsonDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer deserializer) { + if (deserializer instanceof CollectionDeserializer) { + return new PatchableYamlListDeserializer((CollectionDeserializer) deserializer); + } + return deserializer; + } + }); + } + }; + return registerBasicSerializersAndDeserializers(module, resultingMapper); } private SimpleModule createModuleForNonUpgradablePlans(ObjectMapper resultingMapper) { @@ -272,7 +310,7 @@ public Plan yamlPlanToPlan(YamlPlan yamlPlan) { } public YamlPlan planToYamlPlan(Plan plan) { - YamlPlan yamlPlan = new YamlPlan(); + YamlPlan yamlPlan = new YamlPlan(new PatchingContext("", yamlMapper)); yamlPlan.setName(plan.getAttribute(AbstractOrganizableObject.NAME)); yamlPlan.setVersion(currentVersion.toString()); yamlPlan.setCategories(plan.getCategories()); 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 06d9735d80..2838c33b70 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 @@ -19,10 +19,8 @@ package step.plans.parser.yaml.deserializers; import com.fasterxml.jackson.core.JsonParser; -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.*; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import org.everit.json.schema.ValidationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +41,7 @@ import static step.plans.parser.yaml.migrations.AbstractYamlPlanMigrationTask.YAML_PLANS_COLLECTION_NAME; -public class UpgradableYamlPlanDeserializer extends JsonDeserializer { +public class UpgradableYamlPlanDeserializer extends JsonDeserializer implements ResolvableDeserializer { private static final Logger log = LoggerFactory.getLogger(UpgradableYamlPlanDeserializer.class); private final Version currentVersion; @@ -51,11 +49,14 @@ public class UpgradableYamlPlanDeserializer extends JsonDeserializer { private final ObjectMapper yamlMapper; private final String jsonSchema; - public UpgradableYamlPlanDeserializer(Version currentVersion, String jsonSchema, MigrationManager migrationManager, ObjectMapper nonUpgradableYamlMapper) { + private JsonDeserializer delegate; + + public UpgradableYamlPlanDeserializer(Version currentVersion, String jsonSchema, MigrationManager migrationManager, ObjectMapper nonUpgradableYamlMapper, JsonDeserializer delegate) { this.currentVersion = currentVersion; this.jsonSchema = jsonSchema; this.migrationManager = migrationManager; this.yamlMapper = nonUpgradableYamlMapper; + this.delegate = delegate; } @Override @@ -115,7 +116,21 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO } } - return yamlMapper.treeToValue(planJsonNode, YamlPlan.class); + p = planJsonNode.traverse(p.getCodec()); + p.nextToken(); + + return (YamlPlan) delegate.deserialize(p, ctxt); } +/* + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException { + ctxt.findNonContextualValueDeserializer(ctxt.constructType(YamlPlan.class)); + }*/ + @Override + public void resolve(DeserializationContext ctxt) throws JsonMappingException { + if (delegate instanceof ResolvableDeserializer) { + ((ResolvableDeserializer) delegate).resolve(ctxt); + } + } } From 1d0de2c772e82d9f9b9d1453173dc65c5df40713 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 13 Apr 2026 09:16:19 +0200 Subject: [PATCH 17/30] SED-4539 refactor: introduce PatchingContext --- .../deserialization/LocatedJsonArray.java | 51 +++++++++++++++++++ .../yaml/deserialization/LocatedJsonNode.java | 45 ++-------------- .../deserialization/LocatedJsonObject.java | 50 ++++++++++++++++++ .../LocatedYamlObjectFactory.java | 12 ++++- .../LocationAwareTreeTraversingParser.java | 1 - .../PatchingParserDelegate.java | 4 +- 6 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java create mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java new file mode 100644 index 0000000000..7b6c920b40 --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java @@ -0,0 +1,51 @@ +package step.core.yaml.deserialization; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + + +public class LocatedJsonArray extends ArrayNode implements LocatedJsonNode { + + private final PatchingContext patchingContext; + private JsonLocation startLocation; + private JsonLocation endLocation; + + public LocatedJsonArray(JsonNodeFactory nodeFactory, PatchingContext context) { + super(nodeFactory); + this.patchingContext = context; + } + + @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); + } + + @Override + public JsonLocation getStartLocation() { + return startLocation; + } + + @Override + public void setEndLocation(JsonLocation endLocation) { + this.endLocation = endLocation; + } + + @Override + public void setStartLocation(JsonLocation startLocation) { + this.startLocation = startLocation; + } + + @Override + public PatchingContext getPatchingContext() { + return patchingContext; + } +} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java index 16d375777a..401b9ca3e0 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java @@ -1,47 +1,12 @@ package step.core.yaml.deserialization; 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; +public interface LocatedJsonNode { + void setEndLocation(JsonLocation endLocation); + void setStartLocation(JsonLocation endLocation); -public class LocatedJsonNode extends ObjectNode { + JsonLocation getStartLocation(); - private final PatchingContext patchingContext; - private JsonLocation startLocation; - private JsonLocation endLocation; - - public LocatedJsonNode(JsonNodeFactory nodeFactory, PatchingContext context) { - super(nodeFactory); - this.patchingContext = context; - } - - @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 getStartLocation() { - return startLocation; - } - - public void setEndLocation(JsonLocation endLocation) { - this.endLocation = endLocation; - } - - public void setStartLocation(JsonLocation startLocation) { - this.startLocation = startLocation; - } - - public PatchingContext getPatchingContext() { - return patchingContext; - } + PatchingContext getPatchingContext(); } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java new file mode 100644 index 0000000000..41c1f713fa --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java @@ -0,0 +1,50 @@ +package step.core.yaml.deserialization; + +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; + + +public class LocatedJsonObject extends ObjectNode implements LocatedJsonNode { + + private final PatchingContext patchingContext; + private JsonLocation startLocation; + private JsonLocation endLocation; + + public LocatedJsonObject(JsonNodeFactory nodeFactory, PatchingContext context) { + super(nodeFactory); + this.patchingContext = context; + } + + @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 getStartLocation() { + return startLocation; + } + + @Override + public void setEndLocation(JsonLocation endLocation) { + this.endLocation = endLocation; + } + + @Override + public void setStartLocation(JsonLocation startLocation) { + this.startLocation = startLocation; + } + + @Override + public PatchingContext getPatchingContext() { + return patchingContext; + } +} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java index a2e44b27bc..1ec9043808 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java @@ -1,5 +1,6 @@ package step.core.yaml.deserialization; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -13,6 +14,15 @@ public LocatedYamlObjectFactory(PatchingParserDelegate parser) { @Override public ObjectNode objectNode() { - return parser.setCurrentObjectNode(new LocatedJsonNode(this, parser.getPatchingContext())); + LocatedJsonObject node = new LocatedJsonObject(this, parser.getPatchingContext()); + parser.setCurrentObjectNode(node); + return node; + } + + @Override + public ArrayNode arrayNode() { + LocatedJsonArray array = new LocatedJsonArray(this, parser.getPatchingContext()); + parser.setCurrentObjectNode(array); + return array; } } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java index 5629f4a881..070b7f9f5f 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.util.JsonParserDelegate; public class LocationAwareTreeTraversingParser extends PatchingParserDelegate { diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java index 189ed80e0e..09b39202d4 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java @@ -4,7 +4,6 @@ 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 java.io.IOException; import java.util.ArrayDeque; @@ -53,10 +52,9 @@ protected JsonLocation getLastDistinctLocation() { return lastDistinctLocation; } - public ObjectNode setCurrentObjectNode(LocatedJsonNode jsonNode) { + public void setCurrentObjectNode(LocatedJsonNode jsonNode) { jsonNode.setStartLocation(currentLocation()); nodeStack.add(jsonNode); - return jsonNode; } protected PatchingContext getPatchingContext() { From d007114c261abbadce9ea902327c8dedb0a28c08 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 13 Apr 2026 13:11:56 +0200 Subject: [PATCH 18/30] SED-4539 fix: unit tests --- .../yaml/deserialization/YamlKeywordDeserializer.java | 5 ++++- .../main/java/step/core/yaml/PatchableAbstractYamlModel.java | 4 +--- .../src/main/java/step/plans/parser/yaml/YamlPlan.java | 2 +- .../main/java/step/plans/automation/YamlPlainTextPlan.java | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java index 83633eb656..aaa34ff2f2 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java @@ -51,7 +51,10 @@ public YamlAutomationPackageKeyword deserialize(JsonParser jsonParser, Deseriali try { Class clazz = Class.forName(keywordsLookuper.yamlKeywordClassToJava(yamlName)); - return new YamlAutomationPackageKeyword((AbstractYamlFunction) deserializationContext.readValue(jsonParser, clazz)); + jsonParser.nextToken(); + YamlAutomationPackageKeyword keyword = new YamlAutomationPackageKeyword((AbstractYamlFunction) deserializationContext.readValue(jsonParser, clazz)); + jsonParser.nextToken(); + return keyword; } catch (ClassNotFoundException e) { throw new RuntimeException(e); } 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 index 3fe7cf9467..acbf177c55 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java @@ -25,6 +25,7 @@ public class PatchableAbstractYamlModel extends AbstractYamlModel implements PatchableYamlModel { + @JsonIgnore private PatchingContext context; @JsonIgnore @@ -80,9 +81,6 @@ public void setIndent(int indent) { this.indent = indent; } - - - @JsonIgnore @Override public void setContext(PatchingContext context) { this.context = context; 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 1b11804bc5..4d609f429f 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 @@ -51,7 +51,7 @@ public class YamlPlan extends PatchableAbstractYamlModel { private List categories; @JsonCreator - public YamlPlan(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context) { + public YamlPlan(@JacksonInject(useInput = OptBoolean.FALSE, optional = OptBoolean.TRUE) PatchingContext context) { super(context); } 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 c695055179..dca3078d86 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 @@ -40,7 +40,7 @@ public class YamlPlainTextPlan extends PatchableAbstractYamlModel { private String file; @JsonCreator - public YamlPlainTextPlan(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context) { + public YamlPlainTextPlan(@JacksonInject(useInput = OptBoolean.FALSE, optional = OptBoolean.TRUE) PatchingContext context) { super(context); } From 1a591b35fe7e6e357bc7760565646c61ef55f804 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 13 Apr 2026 18:38:21 +0200 Subject: [PATCH 19/30] SED-4539 feat: extend patchable to additionalFields --- .../AutomationPackageDescriptorReader.java | 23 ++++-- ...AutomationPackageFragmentDeserializer.java | 73 +++++++++++++++++++ ...tomationPackageDescriptorDeserializer.java | 55 ++++++++++++++ ...AutomationPackageFragmentDeserializer.java | 56 ++++++++++++++ .../YamlKeywordDeserializer.java | 12 +-- ...AbstractAutomationPackageFragmentYaml.java | 16 +--- .../AutomationPackageDescriptorYamlImpl.java | 13 +--- .../model/AutomationPackageFragmentYaml.java | 2 +- .../AutomationPackageFragmentYamlImpl.java | 13 +--- .../AutomationPackageParameter.java | 12 ++- .../deserializers/StepYamlDeserializer.java | 35 ++++----- .../StepYamlDeserializerAddOn.java | 4 + .../StepYamlDeserializersScanner.java | 48 +++--------- .../YamlDynamicValueDeserializer.java | 9 +-- ...YamlProtectedDynamicValueDeserializer.java | 9 +-- .../YamlDynamicInputDeserializer.java | 5 +- .../YamlKeywordDefinitionDeserializer.java | 5 +- .../NamedYamlDataSourceDeserializer.java | 5 +- .../automation/AutomationPackageSchedule.java | 13 +++- .../plans/parser/yaml/YamlPlanReader.java | 44 ++++++++--- .../NamedYamlArtefactDeserializer.java | 11 +-- .../UpgradableYamlPlanDeserializer.java | 3 +- .../YamlResourceReferenceDeserializer.java | 5 +- 23 files changed, 318 insertions(+), 153 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java create 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 cedac837fc..5702f0a12b 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 @@ -48,6 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -112,12 +113,22 @@ protected T readAutomationPackageYamlF PatchingContext context = new PatchingContext(yamlDescriptorString, yamlObjectMapper); PatchingParserDelegate parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString), context); yamlObjectMapper.setNodeFactory(new LocatedYamlObjectFactory(parser)); - yamlObjectMapper.setInjectableValues(new InjectableValues.Std() - .addValue(ObjectMapper.class, yamlObjectMapper) - .addValue(AutomationPackageSerializationRegistry.class, serializationRegistry) - .addValue(PatchingContext.class, context) - ); - T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(parser, targetClass); + + Map, Object> injections = new HashMap<>(); + injections.put(AutomationPackageSerializationRegistry.class, serializationRegistry); + injections.put(PatchingContext.class, context); + injections.put(ObjectMapper.class, yamlObjectMapper); + + InjectableValues.Std injectableValues = new InjectableValues.Std(); + injections.forEach(injectableValues::addValue); + + yamlObjectMapper.setInjectableValues(injectableValues); + + T res = yamlObjectMapper.reader() + .withAttributes(injections) + .withAttribute("version", version) + .readValue(parser, targetClass); + res.setPatchingContext(context); logAfterRead(packageName, res); return res; diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java new file mode 100644 index 0000000000..b5f895a028 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.BeanDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.model.AbstractAutomationPackageFragmentYaml; +import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; +import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchingContext; +import step.core.yaml.deserializers.StepYamlDeserializerAddOn; + +import java.io.IOException; +import java.util.List; + +public abstract class AbstractYamlAutomationPackageFragmentDeserializer extends BeanDeserializer implements ContextualDeserializer, ResolvableDeserializer { + + private final BeanDeserializer delegate; + + public AbstractYamlAutomationPackageFragmentDeserializer(BeanDeserializer deserializer) { + super(deserializer); + delegate = deserializer; + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return deserialize(p, ctxt, new AutomationPackageFragmentYamlImpl((PatchingContext) ctxt.getAttribute(PatchingContext.class))); + } + + @Override + protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt, Object intoValue, String propName) throws IOException { + try { + AutomationPackageSerializationRegistry registry = (AutomationPackageSerializationRegistry) ctxt.getAttribute(AutomationPackageSerializationRegistry.class); + Class targetClass = registry.resolveClassForYamlField(propName); + JavaType listType = ctxt.getTypeFactory() + .constructCollectionType(PatchableYamlList.class, targetClass); + List list = ctxt.readValue(p, listType); + + AbstractAutomationPackageFragmentYaml fragment = (AbstractAutomationPackageFragmentYaml) intoValue; + fragment.setAdditionalFields(propName, list); + } catch (ClassCastException | NullPointerException e) { + super.handleUnknownVanilla(p, ctxt, intoValue, propName); + } + } + + @Override + public void resolve(DeserializationContext ctxt) throws JsonMappingException { + if (_delegateDeserializer instanceof ResolvableDeserializer) { + ((ResolvableDeserializer) _delegateDeserializer).resolve(ctxt); + } + } + +} 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 new file mode 100644 index 0000000000..c010ebdd43 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageDescriptorDeserializer.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.JsonParser; +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.BeanDeserializer; +import step.automation.packages.yaml.model.AutomationPackageDescriptorYamlImpl; +import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; +import step.core.yaml.deserialization.PatchingContext; +import step.core.yaml.deserializers.StepYamlDeserializerAddOn; + +import java.io.IOException; + +@StepYamlDeserializerAddOn(targetClasses = {AutomationPackageDescriptorYamlImpl.class}) +public class YamlAutomationPackageDescriptorDeserializer extends AbstractYamlAutomationPackageFragmentDeserializer { + + private final BeanDeserializer delegate; + + public YamlAutomationPackageDescriptorDeserializer(BeanDeserializer deserializer) { + super(deserializer); + delegate = deserializer; + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return deserialize(p, ctxt, new AutomationPackageDescriptorYamlImpl((PatchingContext) ctxt.getAttribute(PatchingContext.class))); + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + BeanDeserializer resolved = (BeanDeserializer) delegate.createContextual(ctxt, property); + resolved.resolve(ctxt); + return new YamlAutomationPackageDescriptorDeserializer(resolved); + } +} 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 new file mode 100644 index 0000000000..857dc8644c --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.BeanDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.model.AbstractAutomationPackageFragmentYaml; +import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; +import step.core.yaml.deserialization.PatchingContext; +import step.core.yaml.deserializers.StepYamlDeserializerAddOn; + +import java.io.IOException; +import java.util.List; + +@StepYamlDeserializerAddOn(targetClasses = {AutomationPackageFragmentYamlImpl.class}) +public class YamlAutomationPackageFragmentDeserializer extends AbstractYamlAutomationPackageFragmentDeserializer { + + private final BeanDeserializer delegate; + + public YamlAutomationPackageFragmentDeserializer(BeanDeserializer deserializer) { + super(deserializer); + delegate = deserializer; + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return deserialize(p, ctxt, new AutomationPackageFragmentYamlImpl((PatchingContext) ctxt.getAttribute(PatchingContext.class))); + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + BeanDeserializer resolved = (BeanDeserializer) delegate.createContextual(ctxt, property); + resolved.resolve(ctxt); + return new YamlAutomationPackageFragmentDeserializer(resolved); + } +} diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java index aaa34ff2f2..b068b7f933 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlKeywordDeserializer.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonParser; 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 step.automation.packages.model.AbstractYamlFunction; @@ -34,15 +35,10 @@ @StepYamlDeserializerAddOn(targetClasses = {YamlAutomationPackageKeyword.class}) public class YamlKeywordDeserializer extends StepYamlDeserializer { - private final AutomationPackageKeywordsLookuper keywordsLookuper; + private final AutomationPackageKeywordsLookuper keywordsLookuper = new AutomationPackageKeywordsLookuper(); - public YamlKeywordDeserializer() { - this(null); - } - - public YamlKeywordDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); - this.keywordsLookuper = new AutomationPackageKeywordsLookuper(); + public YamlKeywordDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override 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 f1188629a7..3ce34bc682 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 @@ -41,8 +41,6 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) 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 PatchableYamlList plans; @@ -52,9 +50,7 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio private PatchingContext context; private long fileLastModified = 0; - public AbstractAutomationPackageFragmentYaml(ObjectMapper mapper, AutomationPackageSerializationRegistry serializationRegistry, PatchingContext patchingContext) { - this.mapper = mapper; - this.serializationRegistry = serializationRegistry; + public AbstractAutomationPackageFragmentYaml(PatchingContext patchingContext) { context = patchingContext; plans = new PatchableYamlList<>(patchingContext, YamlPlan.PLANS_ENTITY_NAME); } @@ -97,16 +93,8 @@ public Map> getAdditionalFields() { return 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); + public void setAdditionalFields(String key, List list) { additionalFields.put(key, list); } 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 fc0c965c2f..e7be231568 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 @@ -36,17 +36,8 @@ public class AutomationPackageDescriptorYamlImpl extends AbstractAutomationPacka private String name; - @JsonCreator - public AutomationPackageDescriptorYamlImpl( - @JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, - @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry, - @JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext - ) { - super(mapper, serializationRegistry, patchingContext); - } - - public AutomationPackageDescriptorYamlImpl(PatchingContext context) { - this(null, null, context); + public AutomationPackageDescriptorYamlImpl(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext) { + super(patchingContext); } @Override 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 3eb90d041e..52b9f8e5e9 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 @@ -46,7 +46,7 @@ default List getAdditionalField(String k) { return (List) getAdditionalFields().get(k); } - void setAdditionalFields(String key, JsonNode value) throws IOException; + void setAdditionalFields(String key, List 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 6ffe81122e..6d4aecc70a 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 @@ -27,16 +27,7 @@ public class AutomationPackageFragmentYamlImpl extends AbstractAutomationPackageFragmentYaml { - @JsonCreator - public AutomationPackageFragmentYamlImpl( - @JacksonInject(useInput = OptBoolean.FALSE) ObjectMapper mapper, - @JacksonInject(useInput = OptBoolean.FALSE) AutomationPackageSerializationRegistry serializationRegistry, - @JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext - ) { - super(mapper, serializationRegistry, patchingContext); - } - - public AutomationPackageFragmentYamlImpl(PatchingContext context) { - this(null, null, context); + public AutomationPackageFragmentYamlImpl(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext) { + super(patchingContext); } } diff --git a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java index 50562e93a4..c3527ab918 100644 --- a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java +++ b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java @@ -18,16 +18,21 @@ ******************************************************************************/ package step.parameter.automation; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; import step.commons.activation.Expression; import step.core.dynamicbeans.DynamicValue; import step.core.yaml.AbstractYamlModel; +import step.core.yaml.PatchableAbstractYamlModel; import step.core.yaml.YamlFieldCustomCopy; import step.core.yaml.YamlModel; +import step.core.yaml.deserialization.PatchingContext; import step.parameter.Parameter; import step.parameter.ParameterScope; @YamlModel(named = false) -public class AutomationPackageParameter extends AbstractYamlModel { +public class AutomationPackageParameter extends PatchableAbstractYamlModel { protected String key; protected DynamicValue value; @@ -41,6 +46,11 @@ public class AutomationPackageParameter extends AbstractYamlModel { protected ParameterScope scope = ParameterScope.GLOBAL; protected String scopeEntity; + @JsonCreator + public AutomationPackageParameter(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context) { + super(context); + } + public Parameter toParameter() { Parameter res = new Parameter(); copyFieldsToObject(res, true); diff --git a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializer.java b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializer.java index 5b1cd211ef..ef701ab617 100644 --- a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializer.java +++ b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializer.java @@ -18,35 +18,26 @@ ******************************************************************************/ package step.core.yaml.deserializers; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import java.io.IOException; +public abstract class StepYamlDeserializer extends JsonDeserializer implements ResolvableDeserializer { -public abstract class StepYamlDeserializer extends JsonDeserializer { + protected final ObjectMapper yamlObjectMapper; + protected final JsonDeserializer baseDeserializer; - protected ObjectMapper yamlObjectMapper; - - public StepYamlDeserializer() { - } - - public StepYamlDeserializer(ObjectMapper yamlObjectMapper) { + public StepYamlDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + this.baseDeserializer = deserializer; this.yamlObjectMapper = yamlObjectMapper; } - protected JsonDeserializer getDefaultDeserializerForClass(JsonParser p, DeserializationContext ctxt, Class clazz) throws IOException { - - DeserializationConfig config = ctxt.getConfig(); - JavaType type = TypeFactory.defaultInstance().constructType(clazz); - JsonDeserializer defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type)); - - if (defaultDeserializer instanceof ResolvableDeserializer) { - ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); + @Override + public void resolve(DeserializationContext ctxt) throws JsonMappingException { + if (baseDeserializer instanceof ResolvableDeserializer) { + ((ResolvableDeserializer) baseDeserializer).resolve(ctxt); } - - return defaultDeserializer; } } diff --git a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializerAddOn.java b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializerAddOn.java index f13670a507..4bc3836f42 100644 --- a/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializerAddOn.java +++ b/step-core/src/main/java/step/core/yaml/deserializers/StepYamlDeserializerAddOn.java @@ -18,6 +18,9 @@ ******************************************************************************/ package step.core.yaml.deserializers; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -35,4 +38,5 @@ String LOCATION = "step"; Class[] targetClasses(); + } 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 8051aba5eb..0e71bfc417 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 @@ -18,16 +18,11 @@ ******************************************************************************/ package step.core.yaml.deserializers; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import step.core.scanner.CachedAnnotationScanner; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; +import java.util.*; public class StepYamlDeserializersScanner { @@ -36,41 +31,20 @@ public class StepYamlDeserializersScanner { /** * Scans and returns all {@link StepYamlDeserializer} classes annotated with {@link StepYamlDeserializerAddOn} */ - public static List> scanDeserializerAddons(ObjectMapper yamlObjectMapper) { - List> result = new ArrayList<>(); + public static Map, Class> scanDeserializerAddons() { + Map, Class> result = new HashMap<>(); List> annotatedClasses = new ArrayList<>(CachedAnnotationScanner.getClassesWithAnnotation(StepYamlDeserializerAddOn.LOCATION, StepYamlDeserializerAddOn.class, Thread.currentThread().getContextClassLoader())); for (Class annotatedClass : annotatedClasses) { - if (StepYamlDeserializer.class.isAssignableFrom(annotatedClass)) { - StepYamlDeserializerAddOn annotation = annotatedClass.getAnnotation(StepYamlDeserializerAddOn.class); - Arrays.stream(annotation.targetClasses()).forEach(aClass -> { - try { - StepYamlDeserializer newDeserializer = (StepYamlDeserializer) annotatedClass.getConstructor(ObjectMapper.class).newInstance(yamlObjectMapper); - result.add(new DeserializerBind<>((Class) aClass, newDeserializer)); - } catch (Exception e) { - throw new RuntimeException("Cannot prepare deserializer", e); - } - }); - } + StepYamlDeserializerAddOn annotation = annotatedClass.getAnnotation(StepYamlDeserializerAddOn.class); + Arrays.stream(annotation.targetClasses()).forEach(aClass -> { + try { + result.put(aClass, annotatedClass); + } catch (Exception e) { + throw new RuntimeException("Cannot prepare deserializer", e); + } + }); } return result; } - - public static SimpleModule addAllDeserializerAddonsToModule(SimpleModule module, ObjectMapper yamlObjectMapper) { - SimpleModule res = module; - for (StepYamlDeserializersScanner.DeserializerBind deser : StepYamlDeserializersScanner.scanDeserializerAddons(yamlObjectMapper)) { - res = module.addDeserializer((Class) deser.clazz, deser.deserializer); - } - return res; - } - - public static class DeserializerBind { - public Class clazz; - public StepYamlDeserializer deserializer; - - public DeserializerBind(Class clazz, StepYamlDeserializer deserializer) { - this.clazz = clazz; - this.deserializer = deserializer; - } - } } diff --git a/step-core/src/main/java/step/core/yaml/deserializers/YamlDynamicValueDeserializer.java b/step-core/src/main/java/step/core/yaml/deserializers/YamlDynamicValueDeserializer.java index c28309fd7d..4becc05c2c 100644 --- a/step-core/src/main/java/step/core/yaml/deserializers/YamlDynamicValueDeserializer.java +++ b/step-core/src/main/java/step/core/yaml/deserializers/YamlDynamicValueDeserializer.java @@ -33,17 +33,14 @@ public class YamlDynamicValueDeserializer extends StepYamlDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { this.type = property.getType().containedType(0); - YamlDynamicValueDeserializer deserializer = new YamlDynamicValueDeserializer(); + YamlDynamicValueDeserializer deserializer = new YamlDynamicValueDeserializer(baseDeserializer, yamlObjectMapper); deserializer.type = type; return deserializer; } diff --git a/step-core/src/main/java/step/core/yaml/deserializers/YamlProtectedDynamicValueDeserializer.java b/step-core/src/main/java/step/core/yaml/deserializers/YamlProtectedDynamicValueDeserializer.java index 7aa9134ad1..d7e06ea128 100644 --- a/step-core/src/main/java/step/core/yaml/deserializers/YamlProtectedDynamicValueDeserializer.java +++ b/step-core/src/main/java/step/core/yaml/deserializers/YamlProtectedDynamicValueDeserializer.java @@ -25,17 +25,14 @@ @StepYamlDeserializerAddOn(targetClasses = {ProtectedDynamicValue.class}) public class YamlProtectedDynamicValueDeserializer extends YamlDynamicValueDeserializer { - public YamlProtectedDynamicValueDeserializer() { - } - - public YamlProtectedDynamicValueDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); + public YamlProtectedDynamicValueDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { this.type = property.getType().containedType(0); - YamlProtectedDynamicValueDeserializer deserializer = new YamlProtectedDynamicValueDeserializer(); + YamlProtectedDynamicValueDeserializer deserializer = new YamlProtectedDynamicValueDeserializer(baseDeserializer, yamlObjectMapper); deserializer.type = type; return deserializer; } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlDynamicInputDeserializer.java b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlDynamicInputDeserializer.java index 6feeaec254..244617c31f 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlDynamicInputDeserializer.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlDynamicInputDeserializer.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; 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.node.ArrayNode; @@ -42,8 +43,8 @@ public class YamlDynamicInputDeserializer extends StepYamlDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } /** diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlKeywordDefinitionDeserializer.java b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlKeywordDefinitionDeserializer.java index 57987fb49b..25ed34140f 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlKeywordDefinitionDeserializer.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/YamlKeywordDefinitionDeserializer.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonParser; 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 step.core.accessors.AbstractOrganizableObject; @@ -39,8 +40,8 @@ public class YamlKeywordDefinitionDeserializer extends StepYamlDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceDeserializer.java b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceDeserializer.java index 3b375e5065..2d022bd452 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceDeserializer.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceDeserializer.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonParser; 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 step.core.yaml.deserializers.NamedEntityYamlDeserializer; @@ -31,8 +32,8 @@ @StepYamlDeserializerAddOn(targetClasses = {NamedYamlDataSource.class}) public class NamedYamlDataSourceDeserializer extends StepYamlDeserializer { - public NamedYamlDataSourceDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); + public NamedYamlDataSourceDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override diff --git a/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java b/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java index 050c380832..0e3d2c31f7 100644 --- a/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java +++ b/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java @@ -18,10 +18,16 @@ ******************************************************************************/ package step.core.scheduler.automation; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.OptBoolean; +import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.deserialization.PatchingContext; + import java.util.Map; import java.util.List; -public class AutomationPackageSchedule { +public class AutomationPackageSchedule extends PatchableAbstractYamlModel { public static final String SCHEDULE_DEF = "ScheduleDef"; public static final String FIELD_NAME_IN_AP = "schedules"; @@ -34,10 +40,13 @@ public class AutomationPackageSchedule { private String assertionPlanName; private Map executionParameters; - public AutomationPackageSchedule() { + @JsonCreator + public AutomationPackageSchedule(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext patchingContext) { + super(patchingContext); } public AutomationPackageSchedule(String name, String cron, String planName, Map executionParameters) { + super(new PatchingContext()); this.name = name; this.cron = cron; this.planName = planName; 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 e077339ce6..f5cd0b128d 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 @@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.BeanDeserializer; +import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -33,6 +35,7 @@ import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.core.Version; import step.core.accessors.AbstractOrganizableObject; import step.core.accessors.DefaultJacksonMapperProvider; @@ -43,9 +46,12 @@ import step.core.scanner.AnnotationScanner; import step.core.scanner.CachedAnnotationScanner; import step.core.yaml.PatchableYamlModel; +import step.core.yaml.deserialization.PatchableYamlList; import step.core.yaml.deserialization.PatchableYamlListDeserializer; import step.core.yaml.deserialization.PatchableYamlModelDeserializer; import step.core.yaml.deserialization.PatchingContext; +import step.core.yaml.deserializers.StepYamlDeserializer; +import step.core.yaml.deserializers.StepYamlDeserializerAddOn; import step.core.yaml.deserializers.StepYamlDeserializersScanner; import step.core.yaml.serializers.StepYamlSerializersScanner; import step.migration.MigrationManager; @@ -63,6 +69,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; @@ -216,15 +223,13 @@ public static ObjectMapper createDefaultYamlMapper() { return DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); } - private SimpleModule registerBasicSerializersAndDeserializers(SimpleModule module, ObjectMapper resultingMapper) { - SimpleModule res = StepYamlDeserializersScanner.addAllDeserializerAddonsToModule(module, resultingMapper); - res = StepYamlSerializersScanner.addAllSerializerAddonsToModule(res, resultingMapper); - return res; - } - public SimpleModule registerAllSerializersAndDeserializers(ObjectMapper resultingMapper, boolean upgradablePlan) { ObjectMapper nonUpgradableYamlMapper = createDefaultYamlMapper().registerModule(createModuleForNonUpgradablePlans(resultingMapper)); // configure custom deserializers + + Map, Class> deserializers = StepYamlDeserializersScanner.scanDeserializerAddons(); + + SimpleModule module = new SimpleModule() { @Override public void setupModule(SetupContext context) { @@ -233,10 +238,30 @@ public void setupModule(SetupContext context) { context.addBeanDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { + if (YamlPlan.class == beanDesc.getBeanClass()) { deserializer = new UpgradableYamlPlanDeserializer(upgradablePlan ? currentVersion : null, jsonSchema, migrationManager, nonUpgradableYamlMapper, deserializer); } + if (deserializers.containsKey(beanDesc.getBeanClass())) { + try { + Class deserializerClass = deserializers.get(beanDesc.getBeanClass()); + if (StepYamlDeserializer.class.isAssignableFrom(deserializerClass)) { + deserializer = (JsonDeserializer) deserializerClass.getConstructor(JsonDeserializer.class, ObjectMapper.class).newInstance(deserializer, resultingMapper); + } else if (BeanDeserializer.class.isAssignableFrom(deserializerClass) && BeanDeserializer.class.isAssignableFrom(deserializer.getClass())) { + deserializer = (JsonDeserializer) deserializerClass.getConstructor(BeanDeserializer.class).newInstance((BeanDeserializer) deserializer); + } + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + if (PatchableYamlModel.class.isAssignableFrom(beanDesc.getBeanClass()) && !beanDesc.getBeanClass().equals(PatchableYamlModel.class)) { return new PatchableYamlModelDeserializer<>(deserializer); @@ -246,7 +271,7 @@ public JsonDeserializer modifyDeserializer(DeserializationConfig config, Bean @Override public JsonDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer deserializer) { - if (deserializer instanceof CollectionDeserializer) { + if (deserializer instanceof CollectionDeserializer && beanDesc.getBeanClass().equals(PatchableYamlList.class)) { return new PatchableYamlListDeserializer((CollectionDeserializer) deserializer); } return deserializer; @@ -254,13 +279,12 @@ public JsonDeserializer modifyCollectionDeserializer(DeserializationConfig co }); } }; - return registerBasicSerializersAndDeserializers(module, resultingMapper); + return StepYamlSerializersScanner.addAllSerializerAddonsToModule(module, resultingMapper); } private SimpleModule createModuleForNonUpgradablePlans(ObjectMapper resultingMapper) { SimpleModule module = new SimpleModule(); - registerBasicSerializersAndDeserializers(module, resultingMapper); - return module; + return StepYamlSerializersScanner.addAllSerializerAddonsToModule(module, resultingMapper); } protected ObjectMapper getYamlMapper() { diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/NamedYamlArtefactDeserializer.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/NamedYamlArtefactDeserializer.java index e81194cc42..e696f9275c 100644 --- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/NamedYamlArtefactDeserializer.java +++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/deserializers/NamedYamlArtefactDeserializer.java @@ -19,9 +19,7 @@ package step.plans.parser.yaml.deserializers; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.ObjectNode; import step.core.artefacts.AbstractArtefact; import step.core.yaml.deserializers.NamedEntityYamlDeserializer; @@ -39,12 +37,9 @@ @StepYamlDeserializerAddOn(targetClasses = {NamedYamlArtefact.class}) public class NamedYamlArtefactDeserializer extends StepYamlDeserializer { - public NamedYamlArtefactDeserializer() { - this(null); - } - public NamedYamlArtefactDeserializer(ObjectMapper stepYamlObjectMapper) { - super(stepYamlObjectMapper); + public NamedYamlArtefactDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override 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 2838c33b70..bbaa1eea60 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 @@ -100,8 +100,7 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO if (log.isDebugEnabled()) { log.debug("Yaml plan after migrations: {}", bufferedYamlPlan); } - - planJsonNode = yamlMapper.readTree(bufferedYamlPlan); + planJsonNode = yamlMapper.valueToTree(migratedDocument); } } } diff --git a/step-plans/step-plans-yaml-support/src/main/java/step/plans/parser/yaml/serializers/YamlResourceReferenceDeserializer.java b/step-plans/step-plans-yaml-support/src/main/java/step/plans/parser/yaml/serializers/YamlResourceReferenceDeserializer.java index 5b1c1b8f55..e9d9825e52 100644 --- a/step-plans/step-plans-yaml-support/src/main/java/step/plans/parser/yaml/serializers/YamlResourceReferenceDeserializer.java +++ b/step-plans/step-plans-yaml-support/src/main/java/step/plans/parser/yaml/serializers/YamlResourceReferenceDeserializer.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; 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 step.core.yaml.deserializers.StepYamlDeserializer; @@ -33,8 +34,8 @@ @StepYamlDeserializerAddOn(targetClasses = {YamlResourceReference.class}) public class YamlResourceReferenceDeserializer extends StepYamlDeserializer { - public YamlResourceReferenceDeserializer(ObjectMapper yamlObjectMapper) { - super(yamlObjectMapper); + public YamlResourceReferenceDeserializer(JsonDeserializer deserializer, ObjectMapper yamlObjectMapper) { + super(deserializer, yamlObjectMapper); } @Override From dc93ae4478e47e33c0281c78508327f9b759dc86 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 13 Apr 2026 18:52:15 +0200 Subject: [PATCH 20/30] SED-4539 remove superfluous location aware tree parser --- .../AutomationPackageDescriptorReader.java | 1 - .../deserialization/LocatedJsonArray.java | 51 ------------------- .../yaml/deserialization/LocatedJsonNode.java | 12 ----- .../deserialization/LocatedJsonObject.java | 50 ------------------ .../LocatedYamlObjectFactory.java | 28 ---------- .../LocationAwareTreeTraversingParser.java | 21 -------- .../PatchingParserDelegate.java | 12 ----- 7 files changed, 175 deletions(-) delete mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java delete mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java delete mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java delete mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java delete mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.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 5702f0a12b..df6e75ce12 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 @@ -112,7 +112,6 @@ protected T readAutomationPackageYamlF } PatchingContext context = new PatchingContext(yamlDescriptorString, yamlObjectMapper); PatchingParserDelegate parser = new PatchingParserDelegate(yamlObjectMapper.createParser(yamlDescriptorString), context); - yamlObjectMapper.setNodeFactory(new LocatedYamlObjectFactory(parser)); Map, Object> injections = new HashMap<>(); injections.put(AutomationPackageSerializationRegistry.class, serializationRegistry); diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java deleted file mode 100644 index 7b6c920b40..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonArray.java +++ /dev/null @@ -1,51 +0,0 @@ -package step.core.yaml.deserialization; - -import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; - - -public class LocatedJsonArray extends ArrayNode implements LocatedJsonNode { - - private final PatchingContext patchingContext; - private JsonLocation startLocation; - private JsonLocation endLocation; - - public LocatedJsonArray(JsonNodeFactory nodeFactory, PatchingContext context) { - super(nodeFactory); - this.patchingContext = context; - } - - @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); - } - - @Override - public JsonLocation getStartLocation() { - return startLocation; - } - - @Override - public void setEndLocation(JsonLocation endLocation) { - this.endLocation = endLocation; - } - - @Override - public void setStartLocation(JsonLocation startLocation) { - this.startLocation = startLocation; - } - - @Override - public PatchingContext getPatchingContext() { - return patchingContext; - } -} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java deleted file mode 100644 index 401b9ca3e0..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package step.core.yaml.deserialization; - -import com.fasterxml.jackson.core.JsonLocation; - -public interface LocatedJsonNode { - void setEndLocation(JsonLocation endLocation); - void setStartLocation(JsonLocation endLocation); - - JsonLocation getStartLocation(); - - PatchingContext getPatchingContext(); -} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java deleted file mode 100644 index 41c1f713fa..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedJsonObject.java +++ /dev/null @@ -1,50 +0,0 @@ -package step.core.yaml.deserialization; - -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; - - -public class LocatedJsonObject extends ObjectNode implements LocatedJsonNode { - - private final PatchingContext patchingContext; - private JsonLocation startLocation; - private JsonLocation endLocation; - - public LocatedJsonObject(JsonNodeFactory nodeFactory, PatchingContext context) { - super(nodeFactory); - this.patchingContext = context; - } - - @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 getStartLocation() { - return startLocation; - } - - @Override - public void setEndLocation(JsonLocation endLocation) { - this.endLocation = endLocation; - } - - @Override - public void setStartLocation(JsonLocation startLocation) { - this.startLocation = startLocation; - } - - @Override - public PatchingContext getPatchingContext() { - return patchingContext; - } -} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java deleted file mode 100644 index 1ec9043808..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocatedYamlObjectFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package step.core.yaml.deserialization; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - -public class LocatedYamlObjectFactory extends JsonNodeFactory { - private final PatchingParserDelegate parser; - - public LocatedYamlObjectFactory(PatchingParserDelegate parser) { - this.parser = parser; - - } - - @Override - public ObjectNode objectNode() { - LocatedJsonObject node = new LocatedJsonObject(this, parser.getPatchingContext()); - parser.setCurrentObjectNode(node); - return node; - } - - @Override - public ArrayNode arrayNode() { - LocatedJsonArray array = new LocatedJsonArray(this, parser.getPatchingContext()); - parser.setCurrentObjectNode(array); - return array; - } -} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java b/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java deleted file mode 100644 index 070b7f9f5f..0000000000 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/LocationAwareTreeTraversingParser.java +++ /dev/null @@ -1,21 +0,0 @@ -package step.core.yaml.deserialization; - -import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.core.JsonParser; - - -public class LocationAwareTreeTraversingParser extends PatchingParserDelegate { - - private final LocatedJsonNode sourceNode; - - public LocationAwareTreeTraversingParser(JsonParser delegate, - LocatedJsonNode sourceNode) { - super(delegate, sourceNode.getPatchingContext()); - this.sourceNode = sourceNode; - } - - @Override - public JsonLocation currentLocation() { - return sourceNode.getStartLocation(); - } -} diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java index 09b39202d4..42e1bc574f 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java @@ -17,8 +17,6 @@ public class PatchingParserDelegate extends JsonParserDelegate { private JsonLocation lastDistinctLocation; - private final Deque nodeStack = new ArrayDeque<>(); - protected final PatchingContext patchingContext; public PatchingParserDelegate(JsonParser d, PatchingContext context) { @@ -36,11 +34,6 @@ public JsonToken nextToken() throws IOException { } locationForToken.put(token, preLocation); - if (token == JsonToken.END_OBJECT && !nodeStack.isEmpty()) { - LocatedJsonNode currentNode = nodeStack.pop(); - currentNode.setEndLocation(currentTokenLocation()); - } - return token; } @@ -52,11 +45,6 @@ protected JsonLocation getLastDistinctLocation() { return lastDistinctLocation; } - public void setCurrentObjectNode(LocatedJsonNode jsonNode) { - jsonNode.setStartLocation(currentLocation()); - nodeStack.add(jsonNode); - } - protected PatchingContext getPatchingContext() { return this.patchingContext; } From 62b6f6eee6e403f67d57107a7b486dbb111e4e1e Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Thu, 16 Apr 2026 13:08:13 +0200 Subject: [PATCH 21/30] SED-4539 fix review comments --- .../packages/AutomationPackageReaderTest.java | 30 +++++++++++++++---- .../AutomationPackagePlanCollection.java | 16 ++++++---- .../AutomationPackageCollectionTest.java | 5 ++-- .../packages/AutomationPackageReader.java | 26 +++++++++------- .../packages/JavaAutomationPackageReader.java | 25 +++------------- .../AutomationPackageDescriptorReader.java | 6 +--- .../AutomationPackageYamlFragmentManager.java | 30 +++++++++++-------- ...AbstractAutomationPackageFragmentYaml.java | 1 + .../step/core/yaml/AbstractYamlModel.java | 4 +-- .../step/core/yaml/PatchableYamlModel.java | 18 +++++++++++ ...Model.java => PatchableYamlModelBase.java} | 5 ++-- ...omationPackageConcurrentEditException.java | 18 +++++++++++ .../AutomationPackageUpdateException.java | 18 +++++++++++ .../deserialization/PatchableYamlList.java | 2 +- .../PatchableYamlListDeserializer.java | 18 +++++++++++ .../PatchableYamlModelDeserializer.java | 18 +++++++++++ .../yaml/deserialization/PatchingContext.java | 18 +++++++++++ .../PatchingParserDelegate.java | 18 +++++++++++ .../AutomationPackageParameter.java | 5 ++-- .../packages/AutomationPackageArchive.java | 14 +++++++-- .../JavaAutomationPackageArchive.java | 11 ------- .../java/step/plans/parser/yaml/YamlPlan.java | 4 +-- .../automation/AutomationPackageSchedule.java | 4 +-- .../plans/automation/YamlPlainTextPlan.java | 6 ++-- 24 files changed, 226 insertions(+), 94 deletions(-) rename step-core-model/src/main/java/step/core/yaml/{PatchableAbstractYamlModel.java => PatchableYamlModelBase.java} (91%) diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java index e368b45c67..ae9d4acd8b 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java @@ -71,7 +71,7 @@ public AutomationPackageReaderTest() { public void testReadFromPackage() throws AutomationPackageReadingException { File automationPackageJar = new File("src/test/resources/samples/step-automation-packages-sample1.jar"); - AutomationPackageContent automationPackageContent = reader.readAutomationPackageFromJarFile(automationPackageJar, null, null); + AutomationPackageContent automationPackageContent = readAutomationPackageFromJarFile(automationPackageJar, null, null); assertNotNull(automationPackageContent); // 6 keywords: 4 from descriptor and two from java class with @Keyword annotation @@ -208,7 +208,7 @@ public void testReadFromPackage() throws AutomationPackageReadingException { public void testFragmentsWithPackageAP() throws AutomationPackageReadingException { File automationPackage = FileHelper.getClassLoaderResourceAsFile(this.getClass().getClassLoader(), "step/automation/packages/step-automation-packages.zip"); - AutomationPackageContent automationPackageContent = reader.readAutomationPackageFromJarFile(automationPackage, null, null); + AutomationPackageContent automationPackageContent = readAutomationPackageFromJarFile(automationPackage, null, null); assertNotNull(automationPackageContent); List plans = automationPackageContent.getPlans(); @@ -225,7 +225,7 @@ public void testFragmentsWithExplodedAP() throws AutomationPackageReadingExcepti File tempFolder = FileHelper.createTempFolder(); FileHelper.unzip(this.getClass().getClassLoader().getResourceAsStream("step/automation/packages/step-automation-packages.zip"), tempFolder); - AutomationPackageContent automationPackageContent = reader.readAutomationPackageFromJarFile(tempFolder, null, null); + AutomationPackageContent automationPackageContent = readAutomationPackageFromJarFile(tempFolder, null, null); assertNotNull(automationPackageContent); List plans = automationPackageContent.getPlans(); @@ -241,14 +241,14 @@ public void testFragmentsWithExplodedAP() throws AutomationPackageReadingExcepti public void testInvalidAPNames() { File automationPackage = FileHelper.getClassLoaderResourceAsFile(this.getClass().getClassLoader(), "step/automation/packages/step-automation-packages-invalidNameBackSlash.zip"); try { - reader.readAutomationPackageFromJarFile(automationPackage, null, null); + readAutomationPackageFromJarFile(automationPackage, null, null); fail(); } catch (AutomationPackageReadingException e) { assertEquals("Package name contains unsafe characters: My package\\. Simple quote and backslash characters are not allowed.", e.getMessage()); } automationPackage = FileHelper.getClassLoaderResourceAsFile(this.getClass().getClassLoader(), "step/automation/packages/step-automation-packages-invalidNameSimpleQuote.zip"); try { - reader.readAutomationPackageFromJarFile(automationPackage, null, null); + readAutomationPackageFromJarFile(automationPackage, null, null); fail(); } catch (AutomationPackageReadingException e) { assertEquals("Package name contains unsafe characters: My package';. Simple quote and backslash characters are not allowed.", e.getMessage()); @@ -265,7 +265,7 @@ public void testMissingDescriptor() throws IOException, AutomationPackageReading boolean deleteOk = descriptor.delete(); Assert.assertTrue(deleteOk); - AutomationPackageContent automationPackageContent = reader.readAutomationPackageFromJarFile(tempFolder, null, null); + AutomationPackageContent automationPackageContent = readAutomationPackageFromJarFile(tempFolder, null, null); assertNotNull(automationPackageContent); assertEquals(tempFolder.getName(), automationPackageContent.getName()); @@ -276,4 +276,22 @@ public void testMissingDescriptor() throws IOException, AutomationPackageReading Assert.assertTrue("Temp folder cannot be removed", FileHelper.deleteFolder(tempFolder)); } + + /** + * Convenient method for test + * + * @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 read from the provided files + * @throws AutomationPackageReadingException in case of error + */ + private AutomationPackageContent readAutomationPackageFromJarFile(File automationPackage, String apVersion, File keywordLib) throws AutomationPackageReadingException { + try (JavaAutomationPackageArchive automationPackageArchive = new JavaAutomationPackageArchive(automationPackage, keywordLib, null)) { + return reader.readAutomationPackage(automationPackageArchive, apVersion); + } catch (IOException e) { + throw new AutomationPackageReadingException("IO Exception", e); + } + } + } 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 4ba860257f..216f29857c 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 @@ -1,18 +1,18 @@ /******************************************************************************* * 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 . ******************************************************************************/ @@ -21,6 +21,7 @@ import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.core.collections.inmemory.InMemoryCollection; import step.core.plans.Plan; +import step.plans.parser.yaml.YamlPlan; public class AutomationPackagePlanCollection extends InMemoryCollection implements Collection { @@ -28,8 +29,13 @@ public class AutomationPackagePlanCollection extends InMemoryCollection im private final AutomationPackageYamlFragmentManager fragmentManager; public AutomationPackagePlanCollection(AutomationPackageYamlFragmentManager fragmentManager) { - super(true, "plans"); + super(true, YamlPlan.PLANS_ENTITY_NAME); this.fragmentManager = fragmentManager; + initialzeRecordsFromFragments(fragmentManager); + } + + private void initialzeRecordsFromFragments(AutomationPackageYamlFragmentManager fragmentManager) { + // initialization into the collection memory. Calls super save to avoid calling fragmentManager.savePlan fragmentManager.getPlans().forEach(super::save); } 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 e2cdbf29b7..501e38dbff 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 @@ -30,6 +30,7 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.core.accessors.AbstractOrganizableObject; import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.automation.packages.yaml.YamlAutomationPackageVersions; @@ -74,9 +75,9 @@ public void setUp() throws IOException, AutomationPackageReadingException { destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile(); FileUtils.copyDirectory(sourceDirectory, destinationDirectory); - fragmentManager = reader.provideAutomationPackageYamlFragmentManager(destinationDirectory); + fragmentManager = reader.getAutomationPackageYamlFragmentManager(destinationDirectory); AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(properties, fragmentManager); - planCollection = collectionFactory.getCollection("plan", Plan.class); + planCollection = collectionFactory.getCollection(AbstractOrganizableObject.NAME, Plan.class); } @After 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 72b07722f9..0e9a4ed525 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 @@ -21,26 +21,31 @@ import ch.exense.commons.app.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; 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; import step.functions.Function; +import step.plans.automation.YamlPlainTextPlan; import step.plans.nl.RootArtefactType; import step.plans.nl.parser.PlanParser; -import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlanReader; -import step.plugins.java.GeneralScriptFunction; import step.repositories.parser.StepsParser; -import java.io.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** @@ -123,8 +128,7 @@ protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescr // apply imported fragments recursively if (descriptor != null) { - Map fragmentMap = new HashMap<>(); - fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); + fillAutomationPackageWithImportedFragments(res, descriptor, archive, new HashMap<>()); } return res; } @@ -175,15 +179,15 @@ protected AutomationPackageContent newContentInstance() { abstract protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(T archive, AutomationPackageContent res) throws AutomationPackageReadingException; - public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragmentManager(T archive) throws AutomationPackageReadingException { + public AutomationPackageYamlFragmentManager getAutomationPackageYamlFragmentManager(T archive) throws AutomationPackageReadingException { AutomationPackageDescriptorReader reader = 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); + AutomationPackageContent content = newContentInstance(); + Map fragmentMap = new ConcurrentHashMap<>(); + fillAutomationPackageWithImportedFragments(content, descriptor, archive, fragmentMap); return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); } catch (IOException 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 1c8068e575..11095780a8 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 @@ -238,31 +238,14 @@ private static boolean isCompositeFunction(Keyword annotation) { return annotation.planReference() != null && !annotation.planReference().isBlank(); } - /** - * Convenient method for test - * + /** Reads automation package into a yaml fragment manager * @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 read from the provided files + * @return the automation package fragment manager read from the provided files for editing * @throws AutomationPackageReadingException in case of error */ - public AutomationPackageContent readAutomationPackageFromJarFile(File automationPackage, String apVersion, File keywordLib) throws AutomationPackageReadingException { - try (JavaAutomationPackageArchive automationPackageArchive = new JavaAutomationPackageArchive(automationPackage, keywordLib, null)) { - return readAutomationPackage(automationPackageArchive, apVersion); - } catch (IOException e) { - throw new AutomationPackageReadingException("IO Exception", e); - } - } - - /** Convenient method for test - * @param automationPackage the JAR file to be read - * @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 { + public AutomationPackageYamlFragmentManager getAutomationPackageYamlFragmentManager(File automationPackage) throws AutomationPackageReadingException { try (JavaAutomationPackageArchive automationPackageArchive = new JavaAutomationPackageArchive(automationPackage, null, null)) { - return provideAutomationPackageYamlFragmentManager(automationPackageArchive); + return getAutomationPackageYamlFragmentManager(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 df6e75ce12..462cfa2346 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 @@ -165,7 +165,7 @@ protected String readJsonSchema(String jsonSchemaPath) { } } - public ObjectMapper createYamlObjectMapper() { + private ObjectMapper createYamlObjectMapper() { YAMLFactory yamlFactory = new YAMLFactory(); // Disable native type id to enable conversion to generic Documents @@ -180,10 +180,6 @@ public ObjectMapper createYamlObjectMapper() { return yamlMapper; } - 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 index 5b6caad742..d00672d105 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 @@ -34,6 +34,8 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.text.MessageFormat; @@ -71,9 +73,9 @@ public void setProperties(Properties properties) { public void initializeMaps(AutomationPackageFragmentYaml fragment) { pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - for (YamlPlan p: fragment.getPlans()) { - Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(p); - planToYamlPlan.put(plan, p); + for (YamlPlan yamlPlan: fragment.getPlans()) { + Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(yamlPlan); + planToYamlPlan.put(plan, yamlPlan); planToYamlFragment.put(plan, fragment); } } @@ -82,22 +84,22 @@ public Iterable getPlans() { return planToYamlPlan.keySet(); } - public Plan savePlan(Plan p) { - YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(p); + public synchronized Plan savePlan(Plan plan) { + YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(plan); - AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); + AutomationPackageFragmentYaml fragment = planToYamlFragment.get(plan); if (fragment == null) { - fragment = fragmentForNewPlan(p); - planToYamlFragment.put(p, fragment); + fragment = fragmentForNewPlan(plan); + planToYamlFragment.put(plan, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { - YamlPlan yamlPlan = planToYamlPlan.get(p); + YamlPlan yamlPlan = planToYamlPlan.get(plan); modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); } - planToYamlPlan.put(p, newYamlPlan); + planToYamlPlan.put(plan, newYamlPlan); - return p; + return plan; } private void addFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T newEntity) { @@ -126,7 +128,9 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { URL url = path.toUri().toURL(); - if (pathToYamlFragment.containsKey(url.toString())) return pathToYamlFragment.get(url.toString()); + if (pathToYamlFragment.containsKey(url.toString())) { + return pathToYamlFragment.get(url.toString()); + } PatchingContext context = new PatchingContext("---", descriptorYaml.getPatchingContext().getMapper()); AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); fragment.setFragmentUrl(url); @@ -138,7 +142,7 @@ private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { } public String sanitizeFilename(String inputName) { - return inputName.replaceAll("[^a-zA-Z0-9-_.]", "_"); + return URLEncoder.encode(inputName, Charset.defaultCharset()).replace("+", " "); } public void removePlan(Plan p) { 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 3ce34bc682..baad23a824 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 @@ -89,6 +89,7 @@ public void setFragments(List fragments) { } @JsonAnyGetter + @Override public Map> getAdditionalFields() { return additionalFields; } 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 1da8fa469a..029ed3e0bb 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,7 +19,6 @@ 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,8 +29,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/PatchableYamlModel.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java index 446db56fa8..2dcfad3a80 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModel.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml; import com.fasterxml.jackson.core.JsonLocation; diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java similarity index 91% rename from step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java rename to step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java index acbf177c55..4146a38d8b 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableAbstractYamlModel.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java @@ -20,10 +20,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.databind.ObjectMapper; import step.core.yaml.deserialization.PatchingContext; -public class PatchableAbstractYamlModel extends AbstractYamlModel implements PatchableYamlModel { +public class PatchableYamlModelBase extends AbstractYamlModel implements PatchableYamlModel { @JsonIgnore private PatchingContext context; @@ -37,7 +36,7 @@ public class PatchableAbstractYamlModel extends AbstractYamlModel implements Pat @JsonIgnore private int endOffset = -1; - public PatchableAbstractYamlModel(PatchingContext context) { + public PatchableYamlModelBase(PatchingContext context) { this.context = context; } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java index be13001533..8c9412a879 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageConcurrentEditException.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; public class AutomationPackageConcurrentEditException extends AutomationPackageUpdateException { diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java index dcf496a132..8016ee8342 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; public class AutomationPackageUpdateException extends RuntimeException { diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java index 1bf6ea41fe..7ba84bb908 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlList.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2020, exense GmbH + * Copyright (C) 2026, exense GmbH * * This file is part of STEP * diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java index e650266b7e..192ec9d849 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlListDeserializer.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; import com.fasterxml.jackson.core.JsonLocation; diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java index bed4f7341a..af2993db76 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlModelDeserializer.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; import com.fasterxml.jackson.core.JsonLocation; diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java index 7e9e35ce80..f38bcc6d38 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java index 42e1bc574f..35b664ddea 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java @@ -1,3 +1,21 @@ +/******************************************************************************* + * 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.yaml.deserialization; import com.fasterxml.jackson.core.JsonLocation; diff --git a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java index c3527ab918..a4d515bd29 100644 --- a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java +++ b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java @@ -23,8 +23,7 @@ import com.fasterxml.jackson.annotation.OptBoolean; import step.commons.activation.Expression; import step.core.dynamicbeans.DynamicValue; -import step.core.yaml.AbstractYamlModel; -import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.YamlFieldCustomCopy; import step.core.yaml.YamlModel; import step.core.yaml.deserialization.PatchingContext; @@ -32,7 +31,7 @@ import step.parameter.ParameterScope; @YamlModel(named = false) -public class AutomationPackageParameter extends PatchableAbstractYamlModel { +public class AutomationPackageParameter extends PatchableYamlModelBase { protected String key; protected DynamicValue value; 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 07268c8bfc..915b671095 100644 --- a/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java +++ b/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java @@ -63,8 +63,18 @@ public String getAutomationPackageName() { abstract public URL getDescriptorYamlUrl(); - abstract public InputStream getDescriptorYaml(); - + public InputStream getDescriptorYaml() { + URL url = getDescriptorYamlUrl(); + if (url == null) { + return null; + } + try { + return url.openStream(); + } catch (IOException e) { + return null; + } + } + abstract public InputStream getResourceAsStream(String resourcePath) throws IOException; abstract public URL getResource(String resourcePath); 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 10d97d6e2d..651273088f 100644 --- a/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java +++ b/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java @@ -133,17 +133,6 @@ public URL getDescriptorYamlUrl() { return null; } - @Override - public InputStream getDescriptorYaml() { - for (String metadataFile : METADATA_FILES) { - InputStream yamlDescriptor = classLoaderForMainApFile.getResourceAsStream(metadataFile); - if (yamlDescriptor != null) { - return yamlDescriptor; - } - } - return null; - } - @Override public InputStream getResourceAsStream(String resourcePath) throws IOException { URL url = getResource(resourcePath); 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 4d609f429f..7c78a5b927 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 @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.OptBoolean; -import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.deserialization.PatchingContext; import step.core.yaml.model.NamedYamlArtefact; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -32,7 +32,7 @@ import java.util.List; -public class YamlPlan extends PatchableAbstractYamlModel { +public class YamlPlan extends PatchableYamlModelBase { public static final String PLANS_ENTITY_NAME = "plans"; diff --git a/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java b/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java index 0e3d2c31f7..0a3b4fc65f 100644 --- a/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java +++ b/step-plans/step-plans-core/src/main/java/step/core/scheduler/automation/AutomationPackageSchedule.java @@ -21,13 +21,13 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.OptBoolean; -import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.deserialization.PatchingContext; import java.util.Map; import java.util.List; -public class AutomationPackageSchedule extends PatchableAbstractYamlModel { +public class AutomationPackageSchedule extends PatchableYamlModelBase { public static final String SCHEDULE_DEF = "ScheduleDef"; public static final String FIELD_NAME_IN_AP = "schedules"; 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 dca3078d86..f9bfe54608 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 @@ -21,15 +21,13 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.OptBoolean; -import step.core.yaml.AbstractYamlModel; -import step.core.yaml.PatchableAbstractYamlModel; +import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.deserialization.PatchingContext; import step.plans.nl.RootArtefactType; -import step.core.yaml.PatchableYamlModel; import java.util.List; -public class YamlPlainTextPlan extends PatchableAbstractYamlModel { +public class YamlPlainTextPlan extends PatchableYamlModelBase { private String name; From 7fcd6551e2b13469e9e84fd8ae39468c8a60bf7c Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 20 Apr 2026 18:30:58 +0200 Subject: [PATCH 22/30] SED-4539 modify parameters --- .../AutomationPackageCollectionFactory.java | 11 +- .../AutomationPackageParameterCollection.java | 61 +++++++++ .../AutomationPackagePlanCollection.java | 4 +- .../AutomationPackageCollectionTest.java | 53 +++++--- ...lo_World_Plan.yml => Hello World Plan.yml} | 0 .../expected/parametersAfterModification.yml | 14 ++ .../parameters.yml | 2 +- .../packages/AutomationPackageReader.java | 3 +- .../AutomationPackageDescriptorReader.java | 2 +- .../AutomationPackageYamlFragmentManager.java | 121 ++++++++++++++---- ...AutomationPackageFragmentDeserializer.java | 2 +- ...AbstractAutomationPackageFragmentYaml.java | 6 +- .../model/AutomationPackageFragmentYaml.java | 8 +- .../AutomationPackageParameter.java | 27 +++- .../AutomationPackageHookRegistry.java | 3 +- 15 files changed, 253 insertions(+), 64 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java rename step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/{Hello_World_Plan.yml => Hello World Plan.yml} (100%) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.yml 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 index e3be980294..ca0ef33318 100644 --- 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 @@ -1,18 +1,18 @@ /******************************************************************************* * 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 . ******************************************************************************/ @@ -24,6 +24,7 @@ import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.core.collections.inmemory.InMemoryCollectionFactory; import step.core.plans.Plan; +import step.parameter.Parameter; public class AutomationPackageCollectionFactory implements CollectionFactory { @@ -40,6 +41,8 @@ public Collection getCollection(String name, Class entityClass) { if (entityClass == Plan.class) { return (Collection) new AutomationPackagePlanCollection(fragmentManager); + } else if (entityClass == Parameter.class) { + return (Collection) new AutomationPackageParameterCollection(fragmentManager); } return baseFactory.getCollection(name, entityClass); diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java new file mode 100644 index 0000000000..a7780a210e --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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.parameter.Parameter; +import step.parameter.automation.AutomationPackageParameter; + +public class AutomationPackageParameterCollection extends InMemoryCollection implements Collection { + + + private final AutomationPackageYamlFragmentManager fragmentManager; + + public AutomationPackageParameterCollection(AutomationPackageYamlFragmentManager fragmentManager) { + super(true, Parameter.ENTITY_NAME); + this.fragmentManager = fragmentManager; + initialzeRecordsFromFragments(fragmentManager); + } + + private void initialzeRecordsFromFragments(AutomationPackageYamlFragmentManager fragmentManager) { + // initialization into the collection memory. Calls super save to avoid calling fragmentManager.savePlan + fragmentManager.getBusinessObjects(Parameter.class).forEach(super::save); + } + + @Override + public Parameter save(Parameter parameter){ + return super.save(fragmentManager.saveAdditionalFieldObject(parameter, context -> AutomationPackageParameter.forContext(context, parameter), Parameter.ENTITY_NAME)); + } + + @Override + public void save(Iterable iterable) { + for (Parameter p : iterable) { + save(p); + } + } + + @Override + public void remove(Filter filter) { + find(filter, null, null, null, 0).forEach(parameter -> + fragmentManager.removeAdditionalFieldObject(parameter, Parameter.ENTITY_NAME) + ); + super.remove(filter); + } +} 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 216f29857c..0ba94ff0eb 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 @@ -36,7 +36,7 @@ public AutomationPackagePlanCollection(AutomationPackageYamlFragmentManager frag private void initialzeRecordsFromFragments(AutomationPackageYamlFragmentManager fragmentManager) { // initialization into the collection memory. Calls super save to avoid calling fragmentManager.savePlan - fragmentManager.getPlans().forEach(super::save); + fragmentManager.getBusinessObjects(Plan.class).forEach(super::save); } @Override @@ -53,7 +53,7 @@ public void save(Iterable iterable) { @Override public void remove(Filter filter) { - find(filter, null, null, null, Integer.MAX_VALUE).forEach(fragmentManager::removePlan); + find(filter, null, null, null, 0).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 index 501e38dbff..78f8c2d5a4 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 @@ -36,8 +36,10 @@ import step.automation.packages.yaml.YamlAutomationPackageVersions; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; +import step.parameter.Parameter; import step.parameter.ParameterManager; import step.parameter.automation.AutomationPackageParametersRegistration; +import step.plans.parser.yaml.YamlPlan; import java.io.File; import java.io.IOException; @@ -56,6 +58,7 @@ public class AutomationPackageCollectionTest { private final File sourceDirectory = new File("src/test/resources/samples/step-automation-packages-sample1"); private File destinationDirectory; private Collection planCollection; + private Collection parameterCollection; private final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected"); private AutomationPackageYamlFragmentManager fragmentManager; @@ -77,7 +80,8 @@ public void setUp() throws IOException, AutomationPackageReadingException { fragmentManager = reader.getAutomationPackageYamlFragmentManager(destinationDirectory); AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(properties, fragmentManager); - planCollection = collectionFactory.getCollection(AbstractOrganizableObject.NAME, Plan.class); + planCollection = collectionFactory.getCollection(YamlPlan.PLANS_ENTITY_NAME, Plan.class); + parameterCollection = collectionFactory.getCollection(Parameter.ENTITY_NAME, Parameter.class); } @After @@ -174,9 +178,9 @@ public void testAddPlanToExistingFragmentWithExistingPlans() throws IOException 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); + + setPropertiesWriteToFragment("plans/plan1.yml"); + planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("plan1AfterAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); @@ -207,9 +211,8 @@ public void testPlanModifyAndAdd() throws IOException { 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); + setPropertiesWriteToFragment("plans/plan1.yml"); + planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("plan1AfterModifyAndAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); @@ -240,9 +243,9 @@ public void testPlanModifyAndAddAndRemove() throws IOException { 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); + + setPropertiesWriteToFragment("plans/plan1.yml"); + planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("plan1AfterModifyAndAdd.yml"), destinationDirectory.toPath().resolve("plans").resolve("plan1.yml")); @@ -266,6 +269,8 @@ public void testAddPlanToDescriptorWithPresentButEmptyPlanArray() throws IOExcep attributes.put("name", "New Name"); plan.setAttributes(attributes); + setPropertiesWriteToFragment("automation-package.yml"); + planCollection.save(plan); assertFilesEqual(expectedFilesPath.resolve("descriptorAfterAdd.yml"), destinationDirectory.toPath().resolve("automation-package.yml")); @@ -285,13 +290,23 @@ public void testAddPlanToNewFragment() throws IOException { 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")); + assertFilesEqual(expectedFilesPath.resolve("Hello World Plan.yml"), destinationDirectory.toPath().resolve("plans").resolve("Hello World Plan.yml")); + } + + @Test + public void testParameterModify() throws IOException { + Optional optionalParameter = parameterCollection.find(Filters.equals("key", "mySimpleKey"), null, null, null, 100).findFirst(); + + assertTrue(optionalParameter.isPresent()); + + Parameter parameter = optionalParameter.get(); + + parameter.getValue().setValue("myModifiedValue"); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); } private void assertFilesEqual(Path expected, Path actual) throws IOException { @@ -300,4 +315,12 @@ private void assertFilesEqual(Path expected, Path actual) throws IOException { assertEquals(expectedLines, actualLines); } + + private void setPropertiesWriteToFragment(String fragment) { + Properties properties = new Properties(); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, YamlPlan.PLANS_ENTITY_NAME), fragment); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, YamlPlan.PLANS_ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); + + fragmentManager.setProperties(properties); + } } 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 similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello_World_Plan.yml rename to step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello World Plan.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.yml new file mode 100644 index 0000000000..6bb1b72540 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.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: "myModifiedValue" + - key: myDynamicParam + value: + expression: "mySimpleKey" 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 index 587e90b308..1909ed829c 100644 --- 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 @@ -11,4 +11,4 @@ parameters: value: mySimpleValue - key: myDynamicParam value: - expression: "mySimpleKey" \ No newline at end of file + expression: "mySimpleKey" 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 0e9a4ed525..f2c779af5f 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 @@ -28,6 +28,7 @@ import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.core.plans.Plan; +import step.core.yaml.deserialization.PatchableYamlList; import step.functions.Function; import step.plans.automation.YamlPlainTextPlan; import step.plans.nl.RootArtefactType; @@ -220,7 +221,7 @@ protected void fillContentSections(AutomationPackageContent targetPackage, Autom readPlainTextPlans(targetPackage, fragment, archive); - for (Map.Entry> additionalField : fragment.getAdditionalFields().entrySet()) { + for (Map.Entry> additionalField : fragment.getAdditionalFields().entrySet()) { boolean hooked = hookRegistry.onAdditionalDataRead(additionalField.getKey(), additionalField.getValue(), targetPackage); if (!hooked) { log.warn("Hook not found for additional field " + additionalField.getKey() + ". The additional field has been skipped"); 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 462cfa2346..dac6eabcab 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 @@ -146,7 +146,7 @@ protected void logAfterRead(String pac if (!res.getPlansPlainText().isEmpty()) { log.info("{} plain text plan(s) found in automation package {}", res.getPlans().size(), StringUtils.defaultString(packageName)); } - for (Map.Entry> additionalEntry : res.getAdditionalFields().entrySet()) { + for (Map.Entry> additionalEntry : res.getAdditionalFields().entrySet()) { log.info("{} {} found in automation package {}", additionalEntry.getValue().size(), additionalEntry.getKey(), StringUtils.defaultString(packageName)); } if (!res.getFragments().isEmpty()) { 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 d00672d105..5e5ac684fa 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,40 +18,54 @@ ******************************************************************************/ package step.automation.packages.yaml; -import org.apache.commons.io.FileUtils; -import step.core.yaml.deserialization.AutomationPackageUpdateException; -import step.core.yaml.deserialization.PatchableYamlList; 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.core.yaml.PatchableYamlModel; +import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.deserialization.AutomationPackageUpdateException; +import step.core.yaml.deserialization.PatchableYamlList; import step.core.yaml.deserialization.PatchingContext; +import step.parameter.Parameter; +import step.parameter.automation.AutomationPackageParameter; import step.plans.parser.yaml.YamlPlan; -import java.io.*; +import java.io.File; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.text.MessageFormat; -import java.util.*; +import java.util.Map; +import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.stream.Collectors; public class AutomationPackageYamlFragmentManager { + public enum NewObjectFragmentMode { + /** + * Write new objects into fragment with fixed path. PATH indicates fragment yaml. Default: default is [ap field name].yml + */ + FRAGMENT, + /** + * Write new objects into new fragment, fragment name is given by object name. PATH indicates subfolder of fragment, default is [ap field name]. + */ + PER_OBJECT, + } - public static final String PROPERTY_NEW_PLAN_FRAGMENT_PATH = "newFragmentPaths.plans"; + public static final String PROPERTY_NEW_OBJECT_FRAGMENT_MODE = "newFragmentPaths.%s.mode"; + public static final String PROPERTY_NEW_OBJECT_FRAGMENT_PATH = "newFragmentPaths.%s.path"; private final AutomationPackageDescriptorReader descriptorReader; - private final Map planToYamlPlan = new ConcurrentHashMap<>(); - private final Map planToYamlFragment = new ConcurrentHashMap<>(); + private final Map patchableMap = new ConcurrentHashMap<>(); + private final Map fragmentMap = new ConcurrentHashMap<>(); private final Map pathToYamlFragment; + private Properties properties = new Properties(); private final AutomationPackageFragmentYaml descriptorYaml; @@ -75,29 +89,41 @@ public void initializeMaps(AutomationPackageFragmentYaml fragment) { pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); for (YamlPlan yamlPlan: fragment.getPlans()) { Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(yamlPlan); - planToYamlPlan.put(plan, yamlPlan); - planToYamlFragment.put(plan, fragment); + patchableMap.put(plan, yamlPlan); + fragmentMap.put(plan, fragment); + } + + PatchableYamlList parameters = fragment.getAdditionalField(Parameter.ENTITY_NAME); + if (parameters != null) { + for (Object object : parameters) { + AutomationPackageParameter yamlParameter = (AutomationPackageParameter) object; + Parameter parameter = yamlParameter.toParameter(); + patchableMap.put(parameter, yamlParameter); + fragmentMap.put(parameter, fragment); + } } } - public Iterable getPlans() { - return planToYamlPlan.keySet(); + public Iterable getBusinessObjects(Class boClass) { + return patchableMap.keySet().stream() + .filter(businessObject -> boClass.isAssignableFrom(businessObject.getClass())) + .map(businessObject -> (BO) businessObject).collect(Collectors.toList()); } public synchronized Plan savePlan(Plan plan) { YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(plan); - AutomationPackageFragmentYaml fragment = planToYamlFragment.get(plan); + AutomationPackageFragmentYaml fragment = fragmentMap.get(plan); if (fragment == null) { - fragment = fragmentForNewPlan(plan); - planToYamlFragment.put(plan, fragment); + fragment = fragmentForNewObject(plan, YamlPlan.PLANS_ENTITY_NAME); + fragmentMap.put(plan, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { - YamlPlan yamlPlan = planToYamlPlan.get(plan); + YamlPlan yamlPlan = (YamlPlan) patchableMap.get(plan); modifyFragmentEntity(fragment, fragment.getPlans(), yamlPlan, newYamlPlan); } - planToYamlPlan.put(plan, newYamlPlan); + patchableMap.put(plan, newYamlPlan); return plan; } @@ -112,18 +138,25 @@ private void modifyFragmentEntity(AutomationPacka fragment.writeToDisk(); } - private AutomationPackageFragmentYaml fragmentForNewPlan(Plan p) { + private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableObject p, String fieldName) { - 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(); + NewObjectFragmentMode mode = NewObjectFragmentMode.valueOf(properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.PER_OBJECT.name())); + String defaultRelativeFragmentPath = fieldName; + if (mode == NewObjectFragmentMode.FRAGMENT) { + defaultRelativeFragmentPath = defaultRelativeFragmentPath + ".yml"; + } + String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); + Path path = new File(relativeFragmentPath).toPath(); if (!path.isAbsolute()) { Path apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) .getParent(); path = apRoot.resolve(path); } + if (mode == NewObjectFragmentMode.PER_OBJECT) { + path = path.resolve(sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME)) + ".yml"); + } + try { URL url = path.toUri().toURL(); @@ -146,14 +179,46 @@ public String sanitizeFilename(String inputName) { } public void removePlan(Plan p) { - AutomationPackageFragmentYaml fragment = planToYamlFragment.get(p); - YamlPlan yamlPlan = planToYamlPlan.get(p); + AutomationPackageFragmentYaml fragment = fragmentMap.get(p); + YamlPlan yamlPlan = (YamlPlan) patchableMap.get(p); fragment.getPlans().remove(yamlPlan); - planToYamlPlan.remove(p); - planToYamlFragment.remove(p); + patchableMap.remove(p); + fragmentMap.remove(p); fragment.writeToDisk(); } + + public void removeAdditionalFieldObject(BO object, String fieldName) { + + AutomationPackageFragmentYaml fragment = fragmentMap.get(object); + PatchableYamlModel yamlObject = patchableMap.get(object); + + fragment.getAdditionalField(fieldName) + .remove(yamlObject); + + patchableMap.remove(object); + fragmentMap.remove(object); + + fragment.writeToDisk(); + } + + public synchronized BO saveAdditionalFieldObject(BO object, Function newYamlObjectCreator, String fieldName) { + AutomationPackageFragmentYaml fragment = fragmentMap.get(object); + YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); + PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields().getOrDefault(fieldName, new PatchableYamlList(fragment.getPatchingContext(), fieldName)); + if (fragment == null) { + fragment = fragmentForNewObject(object, fieldName); + fragmentMap.put(object, fragment); + pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); + addFragmentEntity(fragment, list, newYamlObject); + patchableMap.put(object, newYamlObject); + } else { + + YO oldYamlObject = (YO) patchableMap.get(object); + modifyFragmentEntity(fragment, list, oldYamlObject, newYamlObject); + } + return object; + } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java index b5f895a028..d22526a3f1 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java @@ -54,7 +54,7 @@ protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt, O Class targetClass = registry.resolveClassForYamlField(propName); JavaType listType = ctxt.getTypeFactory() .constructCollectionType(PatchableYamlList.class, targetClass); - List list = ctxt.readValue(p, listType); + PatchableYamlList list = ctxt.readValue(p, listType); AbstractAutomationPackageFragmentYaml fragment = (AbstractAutomationPackageFragmentYaml) intoValue; fragment.setAdditionalFields(propName, list); 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 baad23a824..d1895caf73 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 @@ -46,7 +46,7 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio private PatchableYamlList plans; private List plansPlainText = new ArrayList<>(); - private final Map> additionalFields = new HashMap<>(); + private final Map> additionalFields = new HashMap<>(); private PatchingContext context; private long fileLastModified = 0; @@ -90,12 +90,12 @@ public void setFragments(List fragments) { @JsonAnyGetter @Override - public Map> getAdditionalFields() { + public Map> getAdditionalFields() { return additionalFields; } @Override - public void setAdditionalFields(String key, List list) { + public void setAdditionalFields(String key, PatchableYamlList list) { additionalFields.put(key, list); } 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 52b9f8e5e9..1cb9c1c78c 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 @@ -40,13 +40,13 @@ public interface AutomationPackageFragmentYaml { List getFragments(); - Map> getAdditionalFields(); + Map> getAdditionalFields(); - default List getAdditionalField(String k) { - return (List) getAdditionalFields().get(k); + default PatchableYamlList getAdditionalField(String k) { + return (PatchableYamlList) getAdditionalFields().get(k); } - void setAdditionalFields(String key, List value) throws IOException; + void setAdditionalFields(String key, PatchableYamlList value) throws IOException; URL getFragmentUrl(); diff --git a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java index a4d515bd29..a07a308c81 100644 --- a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java +++ b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java @@ -18,9 +18,7 @@ ******************************************************************************/ package step.parameter.automation; -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.OptBoolean; +import com.fasterxml.jackson.annotation.*; import step.commons.activation.Expression; import step.core.dynamicbeans.DynamicValue; import step.core.yaml.PatchableYamlModelBase; @@ -31,6 +29,7 @@ import step.parameter.ParameterScope; @YamlModel(named = false) +@JsonInclude(JsonInclude.Include.NON_DEFAULT) public class AutomationPackageParameter extends PatchableYamlModelBase { protected String key; @@ -42,7 +41,17 @@ public class AutomationPackageParameter extends PatchableYamlModelBase { protected Integer priority; protected Boolean protectedValue = false; + + @JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = ScopeFilter.class) protected ParameterScope scope = ParameterScope.GLOBAL; + + public static class ScopeFilter { + @Override + public boolean equals(Object obj) { + return obj == ParameterScope.GLOBAL; + } + } + protected String scopeEntity; @JsonCreator @@ -90,4 +99,16 @@ public ParameterScope getScope() { public String getScopeEntity() { return scopeEntity; } + + public static AutomationPackageParameter forContext(PatchingContext context, Parameter parameter) { + AutomationPackageParameter yamlParameter = new AutomationPackageParameter(context); + yamlParameter.copyFieldsFromObject(parameter, true); + Expression expression = parameter.getActivationExpression(); + if (expression == null) { + yamlParameter.activationScript = null; + } else { + yamlParameter.activationScript = expression.getScript(); + } + return yamlParameter; + } } diff --git a/step-core/src/main/java/step/automation/packages/AutomationPackageHookRegistry.java b/step-core/src/main/java/step/automation/packages/AutomationPackageHookRegistry.java index 7034fcbaee..32a6b29623 100644 --- a/step-core/src/main/java/step/automation/packages/AutomationPackageHookRegistry.java +++ b/step-core/src/main/java/step/automation/packages/AutomationPackageHookRegistry.java @@ -3,6 +3,7 @@ import step.core.AbstractStepContext; import step.core.objectenricher.ObjectPredicate; import step.core.repositories.ImportResult; +import step.core.yaml.deserialization.PatchableYamlList; import java.util.*; @@ -25,7 +26,7 @@ public List getOrderedHookFieldNames() { /** * On reading the additional fields in yaml representation (additional data should be stored in AutomationPackageContent) */ - public boolean onAdditionalDataRead(String fieldName, List yamlData, AutomationPackageContent targetContent) { + public boolean onAdditionalDataRead(String fieldName, PatchableYamlList yamlData, AutomationPackageContent targetContent) { AutomationPackageHook hook = getHook(fieldName); if (hook != null) { hook.onAdditionalDataRead(fieldName, yamlData, targetContent); From e6e9c0a0fc59d266c3a933daae5bf5654a21e8bd Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 4 May 2026 12:34:38 +0200 Subject: [PATCH 23/30] SED-4539 plan format fixes, re-read ap in test, function reading --- .../AutomationPackageCollectionFactory.java | 7 ++- .../AutomationPackageFunctionCollection.java | 59 +++++++++++++++++++ .../AutomationPackageCollectionTest.java | 10 +++- .../expected/Hello World Plan.yml | 5 +- .../expected/descriptorAfterAdd.yml | 5 +- .../expected/plan1AfterAdd.yml | 5 +- .../expected/plan1AfterModification.yml | 4 +- .../expected/plan1AfterModifyAndAdd.yml | 9 +-- .../expected/plan1AfterRename.yml | 4 +- .../AutomationPackageYamlFragmentManager.java | 48 +++++++++++++-- .../YamlKeywordDeserializer.java | 9 ++- ...AbstractAutomationPackageFragmentYaml.java | 5 +- .../model/AutomationPackageFragmentYaml.java | 2 +- .../yaml/deserialization/PatchingContext.java | 3 +- .../PatchingParserDelegate.java | 4 +- .../model/YamlAutomationPackageKeyword.java | 16 ++--- .../plans/parser/yaml/VersionedYamlPlan.java | 43 ++++++++++++++ .../java/step/plans/parser/yaml/YamlPlan.java | 22 ++----- .../plans/parser/yaml/YamlPlanReader.java | 19 +++--- .../UpgradableYamlPlanDeserializer.java | 10 +--- 20 files changed, 205 insertions(+), 84 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java create mode 100644 step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/VersionedYamlPlan.java 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 index ca0ef33318..82b6af3df5 100644 --- 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 @@ -24,6 +24,7 @@ import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.core.collections.inmemory.InMemoryCollectionFactory; import step.core.plans.Plan; +import step.functions.Function; import step.parameter.Parameter; public class AutomationPackageCollectionFactory implements CollectionFactory { @@ -39,10 +40,12 @@ public AutomationPackageCollectionFactory(Properties properties, AutomationPacka @Override public Collection getCollection(String name, Class entityClass) { - if (entityClass == Plan.class) { + if (Plan.class.isAssignableFrom(entityClass)) { return (Collection) new AutomationPackagePlanCollection(fragmentManager); - } else if (entityClass == Parameter.class) { + } else if (Parameter.class.isAssignableFrom(Parameter.class)) { return (Collection) new AutomationPackageParameterCollection(fragmentManager); + } else if (Function.class.isAssignableFrom(entityClass)) { + return (Collection) new AutomationPackageFunctionCollection(fragmentManager); } return baseFactory.getCollection(name, entityClass); diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java new file mode 100644 index 0000000000..7b9119596a --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.model.YamlAutomationPackageKeyword; +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; +import step.core.collections.inmemory.InMemoryCollection; +import step.functions.Function; + +public class AutomationPackageFunctionCollection extends InMemoryCollection implements Collection { + + + private final AutomationPackageYamlFragmentManager fragmentManager; + + public AutomationPackageFunctionCollection(AutomationPackageYamlFragmentManager fragmentManager) { + super(true, YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME); + this.fragmentManager = fragmentManager; + initialzeRecordsFromFragments(fragmentManager); + } + + private void initialzeRecordsFromFragments(AutomationPackageYamlFragmentManager fragmentManager) { + // initialization into the collection memory. Calls super save to avoid calling fragmentManager.savePlan + fragmentManager.getBusinessObjects(Function.class).forEach(super::save); + } + + @Override + public Function save(Function p){ + return super.save(fragmentManager.saveFunction(p)); + } + + @Override + public void save(Iterable iterable) { + for (Function p : iterable) { + save(p); + } + } + + @Override + public void remove(Filter filter) { + find(filter, null, null, null, 0).forEach(fragmentManager::removeFunction); + 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 index 78f8c2d5a4..5194d4646c 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 @@ -44,8 +44,10 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.*; import java.util.stream.Collectors; @@ -85,7 +87,9 @@ public void setUp() throws IOException, AutomationPackageReadingException { } @After - public void tearDown() throws IOException { + public void tearDown() throws IOException, AutomationPackageReadingException { + // Attempt to re-read the just written Automation package from scratch + reader.getAutomationPackageYamlFragmentManager(destinationDirectory); FileUtils.deleteDirectory(destinationDirectory); } @@ -135,7 +139,9 @@ public void testPlanModifyWithConcurrentEdit() throws IOException { text.setDynamic(true); text.setExpression("new Date().toString();"); - Files.writeString(destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"), "This file was edited", StandardCharsets.UTF_8); + Files.copy(sourceDirectory.toPath().resolve("plans").resolve("plan1.yml"), + destinationDirectory.toPath().resolve("plans").resolve("plan1.yml"), + StandardCopyOption.REPLACE_EXISTING); assertThrows(AutomationPackageConcurrentEditException.class, () -> planCollection.save(plan)); 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 index 877fe4ae44..c1c6fe467e 100644 --- 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 @@ -1,11 +1,8 @@ --- plans: -- version: "1.2.0" - name: "Hello World Plan" +- 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 index ac6f041ffe..de6a8fdfed 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 @@ -13,15 +13,12 @@ alertingRules: BindingValueEqualsPredicate: value: "myValue" plans: -- version: "1.2.0" - name: "New Name" +- name: "New Name" root: sequence: children: - echo: text: "Hello World" - agents: null - categories: null fragments: - "keywords.yml" - "plans/*.yml" 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 index 2b847c9cdb..c6cb8b9ebc 100644 --- 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 @@ -18,15 +18,12 @@ plans: keyword: "MyKeyword2" categories: - "Yaml Plan" -- version: "1.2.0" - name: "New Name" +- name: "New Name" root: sequence: children: - echo: text: "Hello World" - agents: null - categories: null 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/expected/plan1AfterModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml index 642a8f1072..81cc315f24 100644 --- 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 @@ -2,8 +2,7 @@ fragments: [] keywords: [] plans: -- version: "1.2.0" - name: "Test Plan" +- name: "Test Plan" root: testCase: nodeName: "Test Plan" @@ -19,7 +18,6 @@ plans: inputs: - myInput: "myValue" keyword: "MyKeyword2" - agents: null categories: - "Yaml Plan" plansPlainText: 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 index 7c8f7d27ea..237084b8b7 100644 --- 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 @@ -2,8 +2,7 @@ fragments: [] keywords: [] plans: -- version: "1.2.0" - name: "Test Plan" +- name: "Test Plan" root: testCase: nodeName: "Test Plan" @@ -19,18 +18,14 @@ plans: inputs: - myInput: "myValue" keyword: "MyKeyword2" - agents: null categories: - "Yaml Plan" -- version: "1.2.0" - name: "New Name" +- name: "New Name" root: sequence: children: - echo: text: "Hello World" - agents: null - categories: null 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/expected/plan1AfterRename.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml index 8cd5bc5d57..bc4856bdc6 100644 --- 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 @@ -2,8 +2,7 @@ fragments: [] keywords: [] plans: -- version: "1.2.0" - name: "New Plan Name" +- name: "New Plan Name" root: testCase: nodeName: "Test Plan" @@ -18,7 +17,6 @@ plans: inputs: - myInput: "myValue" keyword: "MyKeyword2" - agents: null categories: - "Yaml Plan" plansPlainText: 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 5e5ac684fa..d6df5d3025 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,6 +18,7 @@ ******************************************************************************/ package step.automation.packages.yaml; +import step.automation.packages.model.YamlAutomationPackageKeyword; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; @@ -47,6 +48,8 @@ public class AutomationPackageYamlFragmentManager { + + public enum NewObjectFragmentMode { /** * Write new objects into fragment with fixed path. PATH indicates fragment yaml. Default: default is [ap field name].yml @@ -110,6 +113,7 @@ public Iterable getBusinessObjects(Cl .map(businessObject -> (BO) businessObject).collect(Collectors.toList()); } + public synchronized Plan savePlan(Plan plan) { YamlPlan newYamlPlan = descriptorReader.getPlanReader().planToYamlPlan(plan); @@ -128,6 +132,25 @@ public synchronized Plan savePlan(Plan plan) { return plan; } + + + public synchronized step.functions.Function saveFunction(step.functions.Function function) { + AutomationPackageFragmentYaml fragment = fragmentMap.get(function); + YamlAutomationPackageKeyword newYamlKeyword = new YamlAutomationPackageKeyword(null , fragment.getPatchingContext()); + if (fragment == null) { + fragment = fragmentForNewObject(function, YamlPlan.PLANS_ENTITY_NAME); + fragmentMap.put(function, fragment); + pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); + addFragmentEntity(fragment, fragment.getKeywords(), newYamlKeyword); + } else { + YamlAutomationPackageKeyword yamlKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); + modifyFragmentEntity(fragment, fragment.getKeywords(), yamlKeyword, newYamlKeyword); + } + patchableMap.put(function, newYamlKeyword); + + return function; + } + private void addFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T newEntity) { entityList.add(newEntity); fragment.writeToDisk(); @@ -178,18 +201,31 @@ public String sanitizeFilename(String inputName) { return URLEncoder.encode(inputName, Charset.defaultCharset()).replace("+", " "); } - public void removePlan(Plan p) { - AutomationPackageFragmentYaml fragment = fragmentMap.get(p); - YamlPlan yamlPlan = (YamlPlan) patchableMap.get(p); + public void removePlan(Plan plan) { + AutomationPackageFragmentYaml fragment = fragmentMap.get(plan); + YamlPlan yamlPlan = (YamlPlan) patchableMap.get(plan); fragment.getPlans().remove(yamlPlan); - patchableMap.remove(p); - fragmentMap.remove(p); + patchableMap.remove(plan); + fragmentMap.remove(plan); fragment.writeToDisk(); } + public void removeFunction(step.functions.Function function) { + AutomationPackageFragmentYaml fragment = fragmentMap.get(function); + YamlAutomationPackageKeyword yamlKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); + + fragment.getPlans().remove(yamlKeyword); + + patchableMap.remove(function); + fragmentMap.remove(function); + + fragment.writeToDisk(); + } + + public void removeAdditionalFieldObject(BO object, String fieldName) { AutomationPackageFragmentYaml fragment = fragmentMap.get(object); @@ -221,4 +257,6 @@ public synchronized clazz = Class.forName(keywordsLookuper.yamlKeywordClassToJava(yamlName)); + JsonLocation startItem = patchingParser.currentLocation(); jsonParser.nextToken(); - YamlAutomationPackageKeyword keyword = new YamlAutomationPackageKeyword((AbstractYamlFunction) deserializationContext.readValue(jsonParser, clazz)); + YamlAutomationPackageKeyword keyword = new YamlAutomationPackageKeyword((AbstractYamlFunction) deserializationContext.readValue(jsonParser, clazz), patchingParser.getPatchingContext()); + keyword.setPatchingBounds(startItem, patchingParser.getLastDistinctLocation()); jsonParser.nextToken(); return keyword; } catch (ClassNotFoundException 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 d1895caf73..284bf5155b 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 @@ -42,7 +42,7 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public abstract class AbstractAutomationPackageFragmentYaml implements AutomationPackageFragmentYaml { private List fragments = new ArrayList<>(); - private List keywords = new ArrayList<>(); + private PatchableYamlList keywords; private PatchableYamlList plans; private List plansPlainText = new ArrayList<>(); @@ -53,13 +53,14 @@ public abstract class AbstractAutomationPackageFragmentYaml implements Automatio public AbstractAutomationPackageFragmentYaml(PatchingContext patchingContext) { context = patchingContext; plans = new PatchableYamlList<>(patchingContext, YamlPlan.PLANS_ENTITY_NAME); + keywords = new PatchableYamlList<>(patchingContext, YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME); } @JsonIgnore private URL url; @Override - public List getKeywords() { + public PatchableYamlList getKeywords() { return keywords; } 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 1cb9c1c78c..69f09fe8ee 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 @@ -32,7 +32,7 @@ public interface AutomationPackageFragmentYaml { - List getKeywords(); + PatchableYamlList getKeywords(); PatchableYamlList getPlans(); diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java index f38bcc6d38..8c759474e7 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingContext.java @@ -57,7 +57,7 @@ public void setYaml(String yaml) { this.yaml = yaml; } - private String entityStringWithIndent(Object entity, int indent) { + private String entityStringWithIndent(PatchableYamlModel entity, int indent) { try { String indentString = " ".repeat(indent); return mapper @@ -161,5 +161,6 @@ public void appendEmptyPatchable(PatchableYamlModel patchable) { patchable.setEndOffset(yaml.length()); patchable.setIndent(0); patchables.add(patchable); + yaml += "\n"; } } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java index 35b664ddea..dedf45b116 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchingParserDelegate.java @@ -59,11 +59,11 @@ protected JsonLocation getLastLocationForToken(JsonToken token) { return locationForToken.get(token); } - protected JsonLocation getLastDistinctLocation() { + public JsonLocation getLastDistinctLocation() { return lastDistinctLocation; } - protected PatchingContext getPatchingContext() { + public PatchingContext getPatchingContext() { return this.patchingContext; } } diff --git a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java index 36058ddbf4..d51dd52ba1 100644 --- a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java +++ b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java @@ -18,25 +18,19 @@ ******************************************************************************/ package step.automation.packages.model; -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.OptBoolean; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.StagingAutomationPackageContext; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.core.yaml.AutomationPackageKeywordsLookuper; +import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.deserialization.PatchingContext; import step.functions.Function; -import java.io.IOException; - -public class YamlAutomationPackageKeyword implements AutomationPackageKeyword { +public class YamlAutomationPackageKeyword extends PatchableYamlModelBase implements AutomationPackageKeyword { private AbstractYamlFunction yamlKeyword; - public YamlAutomationPackageKeyword(AbstractYamlFunction yamlKeyword) { + public YamlAutomationPackageKeyword(AbstractYamlFunction yamlKeyword, PatchingContext context) { + super(context); this.yamlKeyword = yamlKeyword; } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/VersionedYamlPlan.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/VersionedYamlPlan.java new file mode 100644 index 0000000000..9f45f796e8 --- /dev/null +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/VersionedYamlPlan.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.plans.parser.yaml; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import step.core.yaml.deserialization.PatchingContext; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder(VersionedYamlPlan.VERSION_FIELD_NAME) +public class VersionedYamlPlan extends YamlPlan { + + // this name should be kept untouched to support the migrations for old versions + public static final String VERSION_FIELD_NAME = "version"; + + private String version; + + public VersionedYamlPlan(PatchingContext context, String version) { + + super(context); + this.version = version; + } + + public String getVersion() { + return version; + } +} 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 7c78a5b927..25f0b1d250 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 @@ -20,26 +20,24 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.OptBoolean; -import step.core.yaml.PatchableYamlModelBase; -import step.core.yaml.deserialization.PatchingContext; -import step.core.yaml.model.NamedYamlArtefact; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import step.core.plans.agents.configuration.AgentProvisioningConfiguration; import step.core.plans.agents.configuration.AgentProvisioningConfigurationDeserializer; import step.core.plans.agents.configuration.AgentProvisioningConfigurationSerializer; -import step.core.plans.agents.configuration.AgentProvisioningConfiguration; +import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.deserialization.PatchingContext; +import step.core.yaml.model.NamedYamlArtefact; import java.util.List; +@JsonInclude(JsonInclude.Include.NON_NULL) public class YamlPlan extends PatchableYamlModelBase { public static final String PLANS_ENTITY_NAME = "plans"; - // this name should be kept untouched to support the migrations for old versions - public static final String VERSION_FIELD_NAME = "version"; - - private String version; private String name; private NamedYamlArtefact root; @@ -71,14 +69,6 @@ public void setRoot(NamedYamlArtefact root) { this.root = root; } - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - public AgentProvisioningConfiguration getAgents() { return agents; } 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 f5cd0b128d..d88f06da38 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 @@ -25,17 +25,14 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.BeanDeserializer; -import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; 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.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.core.Version; import step.core.accessors.AbstractOrganizableObject; import step.core.accessors.DefaultJacksonMapperProvider; @@ -51,7 +48,6 @@ import step.core.yaml.deserialization.PatchableYamlModelDeserializer; import step.core.yaml.deserialization.PatchingContext; import step.core.yaml.deserializers.StepYamlDeserializer; -import step.core.yaml.deserializers.StepYamlDeserializerAddOn; import step.core.yaml.deserializers.StepYamlDeserializersScanner; import step.core.yaml.serializers.StepYamlSerializersScanner; import step.migration.MigrationManager; @@ -172,7 +168,7 @@ public Plan readYamlPlan(InputStream yamlPlanStream) throws IOException, YamlPla * Writes the plan as YAML */ public void writeYamlPlan(OutputStream os, Plan plan) throws IOException { - yamlMapper.writeValue(os, planToYamlPlan(plan)); + yamlMapper.writeValue(os, planToVersionedYamlPlan(plan)); } public void convertFromPlainTextToYaml(String planName, InputStream planTextInputStream, OutputStream yamlOutputStream) throws IOException, StepsParser.ParsingException { @@ -333,10 +329,20 @@ public Plan yamlPlanToPlan(YamlPlan yamlPlan) { return plan; } + public VersionedYamlPlan planToVersionedYamlPlan(Plan plan) { + VersionedYamlPlan yamlPlan = new VersionedYamlPlan(new PatchingContext("", yamlMapper), currentVersion.toString()); + setYamlPlanFieldsFromPlan(yamlPlan, plan); + return yamlPlan; + } + public YamlPlan planToYamlPlan(Plan plan) { YamlPlan yamlPlan = new YamlPlan(new PatchingContext("", yamlMapper)); + setYamlPlanFieldsFromPlan(yamlPlan, plan); + return yamlPlan; + } + + private void setYamlPlanFieldsFromPlan(YamlPlan yamlPlan, Plan plan) { yamlPlan.setName(plan.getAttribute(AbstractOrganizableObject.NAME)); - yamlPlan.setVersion(currentVersion.toString()); yamlPlan.setCategories(plan.getCategories()); yamlPlan.setRoot(new NamedYamlArtefact(AbstractYamlArtefact.toYamlArtefact(plan.getRoot(), yamlMapper))); AgentProvisioningConfiguration agents = plan.getAgents(); @@ -345,7 +351,6 @@ public YamlPlan planToYamlPlan(Plan plan) { !((AutomaticAgentProvisioningConfiguration) agents).mode.equals(AutomaticAgentProvisioningConfiguration.PlanAgentsPoolAutoMode.auto_detect)) { yamlPlan.setAgents(plan.getAgents()); } - return yamlPlan; } } 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 bbaa1eea60..1f2bcaa6d4 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 @@ -33,6 +33,7 @@ import step.core.collections.Filters; import step.core.collections.inmemory.InMemoryCollectionFactory; import step.migration.MigrationManager; +import step.plans.parser.yaml.VersionedYamlPlan; import step.plans.parser.yaml.YamlPlan; import step.plans.parser.yaml.schema.YamlPlanValidationException; @@ -65,7 +66,7 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO if (currentVersion != null) { Document yamlPlanDocument = p.getCodec().treeToValue(planJsonNode, Document.class); - String planVersionString = yamlPlanDocument.getString(YamlPlan.VERSION_FIELD_NAME); + String planVersionString = yamlPlanDocument.getString(VersionedYamlPlan.VERSION_FIELD_NAME); if (planVersionString == null) { planVersionString = (String) ctxt.getAttribute("version"); @@ -89,7 +90,7 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO Document migratedDocument = tempCollection.find(Filters.id(planDocument.getId()), null, null, null, 0).findFirst().orElseThrow(); // set actual version - migratedDocument.replace(YamlPlan.VERSION_FIELD_NAME, currentVersion.toString()); + migratedDocument.replace(VersionedYamlPlan.VERSION_FIELD_NAME, currentVersion.toString()); // remove automatically generated document id migratedDocument.remove(AbstractIdentifiableObject.ID); @@ -120,11 +121,6 @@ public YamlPlan deserialize(JsonParser p, DeserializationContext ctxt) throws IO return (YamlPlan) delegate.deserialize(p, ctxt); } -/* - @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException { - ctxt.findNonContextualValueDeserializer(ctxt.constructType(YamlPlan.class)); - }*/ @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { From 7949029586a9a89371f8fa08b3c9d92b63668461 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 4 May 2026 13:53:09 +0200 Subject: [PATCH 24/30] SED-4539 consistency check for added parameters --- .../AutomationPackageCollectionTest.java | 42 ++++++++++++++----- .../expected/parametersAfterAdd.yml | 17 ++++++++ .../parametersAfterAddAndModification.yml | 17 ++++++++ .../AutomationPackageYamlFragmentManager.java | 15 ++++++- ...kagePerObjectSaveUnsupportedException.java | 26 ++++++++++++ 5 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml create mode 100644 step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.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 5194d4646c..ba0267499d 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 @@ -30,12 +30,12 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.core.accessors.AbstractOrganizableObject; -import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; 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.yaml.deserialization.AutomationPackageConcurrentEditException; +import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; import step.parameter.Parameter; import step.parameter.ParameterManager; import step.parameter.automation.AutomationPackageParametersRegistration; @@ -43,8 +43,6 @@ import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -185,7 +183,7 @@ public void testAddPlanToExistingFragmentWithExistingPlans() throws IOException plan.setAttributes(attributes); - setPropertiesWriteToFragment("plans/plan1.yml"); + setPropertiesWriteToFragment(YamlPlan.PLANS_ENTITY_NAME, "plans/plan1.yml"); planCollection.save(plan); @@ -217,7 +215,7 @@ public void testPlanModifyAndAdd() throws IOException { attributes.put("name", "New Name"); plan.setAttributes(attributes); - setPropertiesWriteToFragment("plans/plan1.yml"); + setPropertiesWriteToFragment(YamlPlan.PLANS_ENTITY_NAME, "plans/plan1.yml"); planCollection.save(plan); @@ -250,7 +248,7 @@ public void testPlanModifyAndAddAndRemove() throws IOException { plan.setAttributes(attributes); - setPropertiesWriteToFragment("plans/plan1.yml"); + setPropertiesWriteToFragment(YamlPlan.PLANS_ENTITY_NAME, "plans/plan1.yml"); planCollection.save(plan); @@ -275,7 +273,7 @@ public void testAddPlanToDescriptorWithPresentButEmptyPlanArray() throws IOExcep attributes.put("name", "New Name"); plan.setAttributes(attributes); - setPropertiesWriteToFragment("automation-package.yml"); + setPropertiesWriteToFragment(YamlPlan.PLANS_ENTITY_NAME, "automation-package.yml"); planCollection.save(plan); @@ -315,6 +313,28 @@ public void testParameterModify() throws IOException { assertFilesEqual(expectedFilesPath.resolve("parametersAfterModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); } + + @Test + public void testParameterAddAndModify() throws IOException { + + + Parameter parameter = new Parameter(null, "addedParameter", "test", "This is an added Parameter before modification"); + assertThrows(AutomationPackagePerObjectSaveUnsupportedException.class, () -> parameterCollection.save(parameter)); + + + setPropertiesWriteToFragment(Parameter.ENTITY_NAME, "parameters.yml"); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterAdd.yml"), destinationDirectory.toPath().resolve("parameters.yml")); + + parameter.setDescription("This is an added Parameter after modification"); + parameter.setValue(new DynamicValue<>("foo")); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterAddAndModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); + } + + private void assertFilesEqual(Path expected, Path actual) throws IOException { String expectedLines = Files.readString(expected); String actualLines = Files.readString(actual); @@ -322,10 +342,10 @@ private void assertFilesEqual(Path expected, Path actual) throws IOException { assertEquals(expectedLines, actualLines); } - private void setPropertiesWriteToFragment(String fragment) { + private void setPropertiesWriteToFragment(String entityName, String fragment) { Properties properties = new Properties(); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, YamlPlan.PLANS_ENTITY_NAME), fragment); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, YamlPlan.PLANS_ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, entityName), fragment); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, entityName), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); fragmentManager.setProperties(properties); } diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml new file mode 100644 index 0000000000..0ab61a81ad --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml @@ -0,0 +1,17 @@ +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" + - key: "addedParameter" + value: "test" + description: "This is an added Parameter before modification" diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml new file mode 100644 index 0000000000..73c02cec44 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml @@ -0,0 +1,17 @@ +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" + - key: "addedParameter" + value: "foo" + description: "This is an added Parameter after modification" 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 d6df5d3025..8d6e00ba4e 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 @@ -26,6 +26,7 @@ import step.core.plans.Plan; import step.core.yaml.PatchableYamlModel; import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; import step.core.yaml.deserialization.AutomationPackageUpdateException; import step.core.yaml.deserialization.PatchableYamlList; import step.core.yaml.deserialization.PatchingContext; @@ -168,6 +169,12 @@ private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableOb if (mode == NewObjectFragmentMode.FRAGMENT) { defaultRelativeFragmentPath = defaultRelativeFragmentPath + ".yml"; } + + + if (mode== NewObjectFragmentMode.PER_OBJECT && !p.hasAttribute(AbstractOrganizableObject.NAME)) { + throw new AutomationPackagePerObjectSaveUnsupportedException("Saving by object name is unsupported for " + fieldName + ", please configure the entity to be stored in a specified single fragment."); + } + String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); Path path = new File(relativeFragmentPath).toPath(); if (!path.isAbsolute()) { @@ -242,15 +249,19 @@ public void removeAdditionalFieldObject(B public synchronized BO saveAdditionalFieldObject(BO object, Function newYamlObjectCreator, String fieldName) { AutomationPackageFragmentYaml fragment = fragmentMap.get(object); - YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); - PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields().getOrDefault(fieldName, new PatchableYamlList(fragment.getPatchingContext(), fieldName)); if (fragment == null) { fragment = fragmentForNewObject(object, fieldName); + + YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); + PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields().getOrDefault(fieldName, new PatchableYamlList(fragment.getPatchingContext(), fieldName)); + fragmentMap.put(object, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); addFragmentEntity(fragment, list, newYamlObject); patchableMap.put(object, newYamlObject); } else { + YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); + PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields().getOrDefault(fieldName, new PatchableYamlList(fragment.getPatchingContext(), fieldName)); YO oldYamlObject = (YO) patchableMap.get(object); modifyFragmentEntity(fragment, list, oldYamlObject, newYamlObject); diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java new file mode 100644 index 0000000000..3010e6f60d --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java @@ -0,0 +1,26 @@ +package step.core.yaml.deserialization; + +/******************************************************************************* + * 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 . + ******************************************************************************/ +public class AutomationPackagePerObjectSaveUnsupportedException extends AutomationPackageUpdateException { + + public AutomationPackagePerObjectSaveUnsupportedException(String s) { + super(s); + } +} From 544998395fd50331b07f453add2e86cd9249da22 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 4 May 2026 14:18:58 +0200 Subject: [PATCH 25/30] SED-4539 improved error message --- .../yaml/AutomationPackageYamlFragmentManager.java | 7 ++++++- ...utomationPackagePerObjectSaveUnsupportedException.java | 4 ++-- .../deserialization/AutomationPackageUpdateException.java | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) 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 8d6e00ba4e..b6ce0544e2 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 @@ -172,7 +172,12 @@ private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableOb if (mode== NewObjectFragmentMode.PER_OBJECT && !p.hasAttribute(AbstractOrganizableObject.NAME)) { - throw new AutomationPackagePerObjectSaveUnsupportedException("Saving by object name is unsupported for " + fieldName + ", please configure the entity to be stored in a specified single fragment."); + throw new AutomationPackagePerObjectSaveUnsupportedException(String.format(""" + Saving by object name is unsupported for %1$s, please configure the entity to be stored in a specified single fragment, i.e. + + %2$s = %1$s.yml + %3$s = %4$s + """, fieldName, String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.FRAGMENT.name())); } String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java index 3010e6f60d..20749728f5 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackagePerObjectSaveUnsupportedException.java @@ -20,7 +20,7 @@ ******************************************************************************/ public class AutomationPackagePerObjectSaveUnsupportedException extends AutomationPackageUpdateException { - public AutomationPackagePerObjectSaveUnsupportedException(String s) { - super(s); + public AutomationPackagePerObjectSaveUnsupportedException(String message) { + super(message); } } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java index 8016ee8342..f0cf471b21 100644 --- a/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/AutomationPackageUpdateException.java @@ -19,11 +19,11 @@ package step.core.yaml.deserialization; public class AutomationPackageUpdateException extends RuntimeException { - public AutomationPackageUpdateException(String s, Exception e) { - super(s, e); + public AutomationPackageUpdateException(String message, Exception e) { + super(message, e); } - public AutomationPackageUpdateException(String s) { - super(s); + public AutomationPackageUpdateException(String message) { + super(message); } } From 056259397476736c4843786feded72612fe94de5 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Mon, 4 May 2026 18:13:02 +0200 Subject: [PATCH 26/30] SED-4539 keyword loading and WIP on composite keyword loading --- .../AutomationPackageCollectionFactory.java | 2 +- .../AutomationPackageCollectionTestBase.java | 103 ++++++++++++++++++ ...utomationPackageKeywordCollectionTest.java | 87 +++++++++++++++ ...omationPackageParameterCollectionTest.java | 85 +++++++++++++++ ... AutomationPackagePlanCollectionTest.java} | 101 +---------------- .../keywordsAfterCompositeModification.yml | 29 +++++ .../jmeterProject1/jmeterProject1.xml | 4 + .../jsProject/jsSample.js | 0 .../keywords.yml | 1 - .../lib/fakeLib.jar | 0 .../nodeProject/nodeSample.ts | 0 .../packages/AutomationPackageReader.java | 5 +- .../AutomationPackageYamlFragmentManager.java | 39 +++++-- .../serialization/YamlKeywordSerializer.java | 47 ++++++++ .../main/java/step/functions/Function.java | 7 +- .../packages/model/AbstractYamlFunction.java | 9 +- .../model/YamlAutomationPackageKeyword.java | 2 +- .../yaml/serializers/StepYamlSerializer.java | 3 - .../YamlDynamicValueSerializer.java | 3 - .../NamedYamlDataSourceSerializer.java | 3 - .../automation/YamlCompositeFunction.java | 24 +++- .../NamedYamlArtefactSerializer.java | 4 - 22 files changed, 431 insertions(+), 127 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java rename step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/{AutomationPackageCollectionTest.java => AutomationPackagePlanCollectionTest.java} (65%) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jsProject/jsSample.js create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/lib/fakeLib.jar create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/nodeProject/nodeSample.ts create mode 100644 step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/serialization/YamlKeywordSerializer.java 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 index 82b6af3df5..8fc07916b4 100644 --- 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 @@ -42,7 +42,7 @@ public Collection getCollection(String name, Class entityClass) { if (Plan.class.isAssignableFrom(entityClass)) { return (Collection) new AutomationPackagePlanCollection(fragmentManager); - } else if (Parameter.class.isAssignableFrom(Parameter.class)) { + } else if (Parameter.class.isAssignableFrom(entityClass)) { return (Collection) new AutomationPackageParameterCollection(fragmentManager); } else if (Function.class.isAssignableFrom(entityClass)) { return (Collection) new AutomationPackageFunctionCollection(fragmentManager); diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java new file mode 100644 index 0000000000..86708a3741 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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 step.artefacts.Echo; +import step.artefacts.Sequence; +import step.automation.packages.AutomationPackageHookRegistry; +import step.automation.packages.AutomationPackageReadingException; +import step.automation.packages.JavaAutomationPackageReader; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; +import step.automation.packages.yaml.YamlAutomationPackageVersions; +import step.core.dynamicbeans.DynamicValue; +import step.core.plans.Plan; +import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; +import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; +import step.parameter.Parameter; +import step.parameter.ParameterManager; +import step.parameter.automation.AutomationPackageParametersRegistration; +import step.plans.parser.yaml.YamlPlan; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +public class AutomationPackageCollectionTestBase { + + private final JavaAutomationPackageReader reader; + + protected final File sourceDirectory = new File("src/test/resources/samples/step-automation-packages-sample1"); + protected File destinationDirectory; + protected final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected"); + protected AutomationPackageYamlFragmentManager fragmentManager; + + public AutomationPackageCollectionTestBase() { + AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); + AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); + + // accessor is not required in this test - we only read the yaml and don't store the result anywhere + AutomationPackageParametersRegistration.registerParametersHooks(hookRegistry, serializationRegistry, Mockito.mock(ParameterManager.class)); + + this.reader = new JavaAutomationPackageReader(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, hookRegistry, serializationRegistry, new Configuration()); + } + + @Before + public void setUp() throws IOException, AutomationPackageReadingException { + destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile(); + FileUtils.copyDirectory(sourceDirectory, destinationDirectory); + + fragmentManager = reader.getAutomationPackageYamlFragmentManager(destinationDirectory); + } + + @After + public void tearDown() throws IOException, AutomationPackageReadingException { + // Attempt to re-read the just written Automation package from scratch + reader.getAutomationPackageYamlFragmentManager(destinationDirectory); + FileUtils.deleteDirectory(destinationDirectory); + } + + + protected void assertFilesEqual(Path expected, Path actual) throws IOException { + String expectedLines = Files.readString(expected); + String actualLines = Files.readString(actual); + + assertEquals(expectedLines, actualLines); + } + + protected void setPropertiesWriteToFragment(String entityName, String fragment) { + Properties properties = new Properties(); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, entityName), fragment); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, entityName), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); + + fragmentManager.setProperties(properties); + } +} diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java new file mode 100644 index 0000000000..f1eae6e93e --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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 org.junit.Before; +import org.junit.Test; +import step.artefacts.Echo; +import step.automation.packages.AutomationPackageReadingException; +import step.automation.packages.model.YamlAutomationPackageKeyword; +import step.core.accessors.AbstractOrganizableObject; +import step.core.artefacts.AbstractArtefact; +import step.core.dynamicbeans.DynamicValue; +import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; +import step.functions.Function; +import step.parameter.Parameter; +import step.plugins.functions.types.CompositeFunction; + +import java.io.IOException; +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 AutomationPackageKeywordCollectionTest extends AutomationPackageCollectionTestBase { + + private Collection functionCollection; + + public AutomationPackageKeywordCollectionTest() { + super(); + } + + @Before + public void setUp() throws IOException, AutomationPackageReadingException { + super.setUp(); + AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(new Properties(), fragmentManager); + functionCollection = collectionFactory.getCollection(YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME, Function.class); + } + + @Test + public void testLoadAllKeywords() throws IOException { + List functions = functionCollection.find(Filters.empty(), null, null, null, 100).collect(Collectors.toList()); + + assertEquals(4, functions.size()); + Set functionNames = functions.stream().map(f -> f.getAttribute(AbstractOrganizableObject.NAME)).collect(Collectors.toSet()); + + assertTrue(functionNames.contains("NodeAutomation")); + assertTrue(functionNames.contains("JMeter keyword from automation package")); + assertTrue(functionNames.contains("Composite keyword from AP")); + assertTrue(functionNames.contains("GeneralScript keyword from AP")); + } + + @Test + public void testModifyCompositeKeyword() throws IOException { + Optional optionalFunction = functionCollection.find(Filters.equals("attributes.name", "Composite keyword from AP"), null, null, null, 100).findFirst(); + assertTrue(optionalFunction.isPresent()); + + CompositeFunction compositeFunction = (CompositeFunction) optionalFunction.get(); + + Echo echo = (Echo) compositeFunction.getPlan().getRoot().getChildren().getFirst(); + echo.setText(new DynamicValue<>("Modified Echo")); + + setPropertiesWriteToFragment(YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME, "keywords.yml"); + functionCollection.save(compositeFunction); + + assertFilesEqual(expectedFilesPath.resolve("keywordsAfterCompositeModification.yml"), destinationDirectory.toPath().resolve("keywords.yml")); + } + +} diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java new file mode 100644 index 0000000000..15823837f4 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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 org.junit.Before; +import org.junit.Test; +import step.automation.packages.AutomationPackageReadingException; +import step.core.dynamicbeans.DynamicValue; +import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; +import step.parameter.Parameter; + +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class AutomationPackageParameterCollectionTest extends AutomationPackageCollectionTestBase { + + private Collection parameterCollection; + + public AutomationPackageParameterCollectionTest() { + super(); + } + + @Before + public void setUp() throws IOException, AutomationPackageReadingException { + super.setUp(); + AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(new Properties(), fragmentManager); + parameterCollection = collectionFactory.getCollection(Parameter.ENTITY_NAME, Parameter.class); + } + + @Test + public void testParameterModify() throws IOException { + Optional optionalParameter = parameterCollection.find(Filters.equals("key", "mySimpleKey"), null, null, null, 100).findFirst(); + + assertTrue(optionalParameter.isPresent()); + + Parameter parameter = optionalParameter.get(); + + parameter.getValue().setValue("myModifiedValue"); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); + } + + + @Test + public void testParameterAddAndModify() throws IOException { + + + Parameter parameter = new Parameter(null, "addedParameter", "test", "This is an added Parameter before modification"); + assertThrows(AutomationPackagePerObjectSaveUnsupportedException.class, () -> parameterCollection.save(parameter)); + + + setPropertiesWriteToFragment(Parameter.ENTITY_NAME, "parameters.yml"); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterAdd.yml"), destinationDirectory.toPath().resolve("parameters.yml")); + + parameter.setDescription("This is an added Parameter after modification"); + parameter.setValue(new DynamicValue<>("foo")); + parameterCollection.save(parameter); + + assertFilesEqual(expectedFilesPath.resolve("parametersAfterAddAndModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); + } + +} 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/AutomationPackagePlanCollectionTest.java similarity index 65% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java rename to step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java index ba0267499d..5ebfb3bb3c 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/AutomationPackagePlanCollectionTest.java @@ -18,77 +18,37 @@ ******************************************************************************/ 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 step.artefacts.Echo; import step.artefacts.Sequence; -import step.automation.packages.AutomationPackageHookRegistry; import step.automation.packages.AutomationPackageReadingException; -import step.automation.packages.JavaAutomationPackageReader; -import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; -import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; -import step.automation.packages.yaml.YamlAutomationPackageVersions; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; -import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; -import step.parameter.Parameter; -import step.parameter.ParameterManager; -import step.parameter.automation.AutomationPackageParametersRegistration; import step.plans.parser.yaml.YamlPlan; -import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.*; import java.util.stream.Collectors; import static org.junit.Assert.*; -public class AutomationPackageCollectionTest { +public class AutomationPackagePlanCollectionTest extends AutomationPackageCollectionTestBase { - 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 Collection parameterCollection; - private final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected"); - private AutomationPackageYamlFragmentManager fragmentManager; - - public AutomationPackageCollectionTest() { - AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); - AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); - // accessor is not required in this test - we only read the yaml and don't store the result anywhere - AutomationPackageParametersRegistration.registerParametersHooks(hookRegistry, serializationRegistry, Mockito.mock(ParameterManager.class)); - - this.reader = new JavaAutomationPackageReader(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, hookRegistry, serializationRegistry, new Configuration()); + public AutomationPackagePlanCollectionTest() { + super(); } @Before public void setUp() throws IOException, AutomationPackageReadingException { - Properties properties = new Properties(); - destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile(); - FileUtils.copyDirectory(sourceDirectory, destinationDirectory); - - fragmentManager = reader.getAutomationPackageYamlFragmentManager(destinationDirectory); - AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(properties, fragmentManager); + super.setUp(); + AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(new Properties(), fragmentManager); planCollection = collectionFactory.getCollection(YamlPlan.PLANS_ENTITY_NAME, Plan.class); - parameterCollection = collectionFactory.getCollection(Parameter.ENTITY_NAME, Parameter.class); - } - - @After - public void tearDown() throws IOException, AutomationPackageReadingException { - // Attempt to re-read the just written Automation package from scratch - reader.getAutomationPackageYamlFragmentManager(destinationDirectory); - FileUtils.deleteDirectory(destinationDirectory); } @Test @@ -298,55 +258,4 @@ public void testAddPlanToNewFragment() throws IOException { assertFilesEqual(expectedFilesPath.resolve("Hello World Plan.yml"), destinationDirectory.toPath().resolve("plans").resolve("Hello World Plan.yml")); } - - @Test - public void testParameterModify() throws IOException { - Optional optionalParameter = parameterCollection.find(Filters.equals("key", "mySimpleKey"), null, null, null, 100).findFirst(); - - assertTrue(optionalParameter.isPresent()); - - Parameter parameter = optionalParameter.get(); - - parameter.getValue().setValue("myModifiedValue"); - parameterCollection.save(parameter); - - assertFilesEqual(expectedFilesPath.resolve("parametersAfterModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); - } - - - @Test - public void testParameterAddAndModify() throws IOException { - - - Parameter parameter = new Parameter(null, "addedParameter", "test", "This is an added Parameter before modification"); - assertThrows(AutomationPackagePerObjectSaveUnsupportedException.class, () -> parameterCollection.save(parameter)); - - - setPropertiesWriteToFragment(Parameter.ENTITY_NAME, "parameters.yml"); - parameterCollection.save(parameter); - - assertFilesEqual(expectedFilesPath.resolve("parametersAfterAdd.yml"), destinationDirectory.toPath().resolve("parameters.yml")); - - parameter.setDescription("This is an added Parameter after modification"); - parameter.setValue(new DynamicValue<>("foo")); - parameterCollection.save(parameter); - - assertFilesEqual(expectedFilesPath.resolve("parametersAfterAddAndModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); - } - - - private void assertFilesEqual(Path expected, Path actual) throws IOException { - String expectedLines = Files.readString(expected); - String actualLines = Files.readString(actual); - - assertEquals(expectedLines, actualLines); - } - - private void setPropertiesWriteToFragment(String entityName, String fragment) { - Properties properties = new Properties(); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, entityName), fragment); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, entityName), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); - - fragmentManager.setProperties(properties); - } } diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml new file mode 100644 index 0000000000..4fadfecdad --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml @@ -0,0 +1,29 @@ +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: "Modified 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" diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml new file mode 100644 index 0000000000..2d1d641e24 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jsProject/jsSample.js b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jsProject/jsSample.js new file mode 100644 index 0000000000..e69de29bb2 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 index 5d33a14488..d6606acdb4 100644 --- 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 @@ -27,4 +27,3 @@ keywords: - 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/lib/fakeLib.jar b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/lib/fakeLib.jar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/nodeProject/nodeSample.ts b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/nodeProject/nodeSample.ts new file mode 100644 index 0000000000..e69de29bb2 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 f2c779af5f..a6e8ba6c49 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 @@ -35,6 +35,7 @@ import step.plans.nl.parser.PlanParser; import step.plans.parser.yaml.YamlPlanReader; import step.repositories.parser.StepsParser; +import step.resources.LocalResourceManagerImpl; import java.io.File; import java.io.IOException; @@ -42,6 +43,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -189,7 +191,8 @@ public AutomationPackageYamlFragmentManager getAutomationPackageYamlFragmentMana AutomationPackageContent content = newContentInstance(); Map fragmentMap = new ConcurrentHashMap<>(); fillAutomationPackageWithImportedFragments(content, descriptor, archive, fragmentMap); - return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); + StagingAutomationPackageContext stagingContext = new StagingAutomationPackageContext(null, AutomationPackageOperationMode.LOCAL, new LocalResourceManagerImpl(Path.of(descriptorURL.getPath()).getParent().toFile()), archive, content, null, null, new HashMap<>()); + return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader(), stagingContext); } catch (IOException e) { throw new AutomationPackageReadingException("Failed to read automation package for editing", 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 b6ce0544e2..e2b0867c19 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,6 +18,8 @@ ******************************************************************************/ package step.automation.packages.yaml; +import step.automation.packages.AutomationPackageOperationMode; +import step.automation.packages.StagingAutomationPackageContext; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; @@ -30,9 +32,12 @@ import step.core.yaml.deserialization.AutomationPackageUpdateException; import step.core.yaml.deserialization.PatchableYamlList; import step.core.yaml.deserialization.PatchingContext; +import step.functions.Function; import step.parameter.Parameter; import step.parameter.automation.AutomationPackageParameter; import step.plans.parser.yaml.YamlPlan; +import step.resources.LocalResourceManagerImpl; +import step.resources.ResourceManager; import java.io.File; import java.net.MalformedURLException; @@ -41,15 +46,17 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.text.MessageFormat; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; import java.util.stream.Collectors; public class AutomationPackageYamlFragmentManager { + private final Path apRoot; + private final StagingAutomationPackageContext stagingContext; public enum NewObjectFragmentMode { /** @@ -73,13 +80,16 @@ public enum NewObjectFragmentMode { 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, StagingAutomationPackageContext stagingContext) { this.descriptorReader = descriptorReader; this.descriptorYaml = descriptorYaml; pathToYamlFragment = fragmentMap; + apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) + .getParent(); + this.stagingContext = stagingContext; initializeMaps(descriptorYaml); pathToYamlFragment.values().forEach(this::initializeMaps); @@ -97,6 +107,19 @@ public void initializeMaps(AutomationPackageFragmentYaml fragment) { fragmentMap.put(plan, fragment); } + for (YamlAutomationPackageKeyword keyword : fragment.getKeywords()) { + try { + Function function = keyword.prepareKeyword(stagingContext); + patchableMap.put(function, keyword); + fragmentMap.put(function, fragment); + } catch (Exception e) { + /* TODO: requires proper handling of keywords + which map to resources or require StagingAutomationPackageContext in another way. + */ + System.out.println(e); + } + } + PatchableYamlList parameters = fragment.getAdditionalField(Parameter.ENTITY_NAME); if (parameters != null) { for (Object object : parameters) { @@ -137,17 +160,17 @@ public synchronized Plan savePlan(Plan plan) { public synchronized step.functions.Function saveFunction(step.functions.Function function) { AutomationPackageFragmentYaml fragment = fragmentMap.get(function); - YamlAutomationPackageKeyword newYamlKeyword = new YamlAutomationPackageKeyword(null , fragment.getPatchingContext()); if (fragment == null) { fragment = fragmentForNewObject(function, YamlPlan.PLANS_ENTITY_NAME); fragmentMap.put(function, fragment); pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - addFragmentEntity(fragment, fragment.getKeywords(), newYamlKeyword); + //addFragmentEntity(fragment, fragment.getKeywords(), newYamlKeyword); } else { YamlAutomationPackageKeyword yamlKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); - modifyFragmentEntity(fragment, fragment.getKeywords(), yamlKeyword, newYamlKeyword); + yamlKeyword.getYamlKeyword().updateFromFunction(function); + modifyFragmentEntity(fragment, fragment.getKeywords(), yamlKeyword, yamlKeyword); } - patchableMap.put(function, newYamlKeyword); + //patchableMap.put(function, y); return function; } @@ -183,8 +206,6 @@ private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableOb String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); Path path = new File(relativeFragmentPath).toPath(); if (!path.isAbsolute()) { - Path apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) - .getParent(); path = apRoot.resolve(path); } @@ -252,7 +273,7 @@ public void removeAdditionalFieldObject(B fragment.writeToDisk(); } - public synchronized BO saveAdditionalFieldObject(BO object, Function newYamlObjectCreator, String fieldName) { + public synchronized BO saveAdditionalFieldObject(BO object, java.util.function.Function newYamlObjectCreator, String fieldName) { AutomationPackageFragmentYaml fragment = fragmentMap.get(object); if (fragment == null) { fragment = fragmentForNewObject(object, fieldName); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/serialization/YamlKeywordSerializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/serialization/YamlKeywordSerializer.java new file mode 100644 index 0000000000..053955a991 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/serialization/YamlKeywordSerializer.java @@ -0,0 +1,47 @@ +package step.automation.packages.yaml.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import step.automation.packages.model.YamlAutomationPackageKeyword; +import step.core.yaml.YamlModelUtils; +import step.core.yaml.deserializers.StepYamlDeserializerAddOn; +import step.core.yaml.serializers.StepYamlSerializer; +import step.core.yaml.serializers.StepYamlSerializerAddOn; + +import java.io.IOException; + +/******************************************************************************* + * 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 . + ******************************************************************************/ +@StepYamlSerializerAddOn(targetClasses = {YamlAutomationPackageKeyword.class}) +public class YamlKeywordSerializer extends StepYamlSerializer { + + + public YamlKeywordSerializer(ObjectMapper yamlObjectMapper) { + super(yamlObjectMapper); + } + + @Override + public void serialize(YamlAutomationPackageKeyword yamlKeyword, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException { + gen.writeStartObject(); + gen.writeFieldName(YamlModelUtils.getEntityNameByClass(yamlKeyword.getYamlKeyword().getClass())); + gen.writeObject(yamlKeyword.getYamlKeyword()); + gen.writeEndObject(); + } +} diff --git a/step-core-model/src/main/java/step/functions/Function.java b/step-core-model/src/main/java/step/functions/Function.java index 0fe4ce5504..08981ec26d 100644 --- a/step-core-model/src/main/java/step/functions/Function.java +++ b/step-core-model/src/main/java/step/functions/Function.java @@ -43,10 +43,13 @@ @JsonTypeInfo(use = Id.CLASS, property = JSON_CLASS_FIELD) public class Function extends AbstractOrganizableObject implements EnricheableObject, EvaluationExpression { + public final static String JSON_CLASS_FIELD = "type"; - protected DynamicValue callTimeout = new DynamicValue<>(180000); - protected JsonObject schema = JsonProviderCache.createObjectBuilder().build(); + public final static DynamicValue DEFAULT_CALL_TIMEOUT = new DynamicValue<>(180000); + protected DynamicValue callTimeout = DEFAULT_CALL_TIMEOUT; + public final static JsonObject DEFAULT_SCHEMA = JsonProviderCache.createObjectBuilder().build(); + protected JsonObject schema = DEFAULT_SCHEMA; protected boolean executeLocally; protected Map tokenSelectionCriteria; diff --git a/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java b/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java index 3111a0880b..451275e59e 100644 --- a/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java +++ b/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java @@ -38,8 +38,8 @@ public abstract class AbstractYamlFunction extends AbstractY @JsonSchema(defaultProvider = DefaultYamlFunctionNameProvider.class) private String name; - private DynamicValue callTimeout; - private JsonObject schema; + private DynamicValue callTimeout = Function.DEFAULT_CALL_TIMEOUT; + private JsonObject schema = Function.DEFAULT_SCHEMA; private boolean executeLocally; @@ -120,6 +120,11 @@ public T applyAutomationPackageContext(StagingAutomationPackageContext context) return res; } + public void updateFromFunction(Function function) { + copyFieldsFromObject(function, false); + } + + public static class DefaultYamlFunctionNameProvider implements JsonSchemaDefaultValueProvider { public DefaultYamlFunctionNameProvider() { diff --git a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java index d51dd52ba1..ed114c6cb9 100644 --- a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java +++ b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java @@ -20,6 +20,7 @@ import step.automation.packages.StagingAutomationPackageContext; import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.YamlModelUtils; import step.core.yaml.deserialization.PatchingContext; import step.functions.Function; @@ -47,5 +48,4 @@ public void setYamlKeyword(AbstractYamlFunction yamlKeyword) { public Function prepareKeyword(StagingAutomationPackageContext context) { return yamlKeyword.applyAutomationPackageContext(context); } - } diff --git a/step-core/src/main/java/step/core/yaml/serializers/StepYamlSerializer.java b/step-core/src/main/java/step/core/yaml/serializers/StepYamlSerializer.java index e96ea52f4d..7d2520d975 100644 --- a/step-core/src/main/java/step/core/yaml/serializers/StepYamlSerializer.java +++ b/step-core/src/main/java/step/core/yaml/serializers/StepYamlSerializer.java @@ -33,9 +33,6 @@ public abstract class StepYamlSerializer extends JsonSerializer { protected ObjectMapper yamlObjectMapper; - public StepYamlSerializer() { - } - public StepYamlSerializer(ObjectMapper yamlObjectMapper) { this.yamlObjectMapper = yamlObjectMapper; } diff --git a/step-core/src/main/java/step/core/yaml/serializers/YamlDynamicValueSerializer.java b/step-core/src/main/java/step/core/yaml/serializers/YamlDynamicValueSerializer.java index be8cf54a01..7725f13cd1 100644 --- a/step-core/src/main/java/step/core/yaml/serializers/YamlDynamicValueSerializer.java +++ b/step-core/src/main/java/step/core/yaml/serializers/YamlDynamicValueSerializer.java @@ -29,9 +29,6 @@ @StepYamlSerializerAddOn(targetClasses = {DynamicValue.class}) public class YamlDynamicValueSerializer extends StepYamlSerializer> { - public YamlDynamicValueSerializer() { - } - public YamlDynamicValueSerializer(ObjectMapper yamlObjectMapper) { super(yamlObjectMapper); } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceSerializer.java b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceSerializer.java index 461cc2bea5..b235e2dffb 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceSerializer.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/artefacts/automation/datasource/NamedYamlDataSourceSerializer.java @@ -37,9 +37,6 @@ public class NamedYamlDataSourceSerializer extends StepYamlSerializer { @YamlFieldCustomCopy @@ -62,7 +68,23 @@ protected void fillDeclaredFields(CompositeFunction res, StagingAutomationPackag } } - public Plan yamlPlanToPlan(YamlPlan yamlPlan) { + @Override + public void updateFromFunction(Function function) { + copyFieldsFromObject(function, false); + + if (function instanceof CompositeFunction) { + Plan plan = ((CompositeFunction) function).getPlan(); + // plan name is optional, the composite function name is used by default + if (this.plan.getName() != null && !this.plan.getName().isEmpty()) { + this.plan.setName(plan.getAttribute(AbstractOrganizableObject.NAME));; + } + ObjectMapper mapper = this.plan.getRoot().getYamlArtefact().getYamlObjectMapper(); + this.plan.setRoot(new NamedYamlArtefact(AbstractYamlArtefact.toYamlArtefact(plan.getRoot(), mapper))); + } + } + + + private Plan yamlPlanToPlan(YamlPlan yamlPlan) { Plan plan = new Plan(yamlPlan.getRoot().getYamlArtefact().toArtefact()); // plan name is optional, the composite function name is used by default diff --git a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/serializers/NamedYamlArtefactSerializer.java b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/serializers/NamedYamlArtefactSerializer.java index 7384b23d5f..3927c0c824 100644 --- a/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/serializers/NamedYamlArtefactSerializer.java +++ b/step-plans/step-plans-yaml-parser/src/main/java/step/plans/parser/yaml/serializers/NamedYamlArtefactSerializer.java @@ -47,10 +47,6 @@ public class NamedYamlArtefactSerializer extends StepYamlSerializer Date: Tue, 5 May 2026 06:44:23 +0200 Subject: [PATCH 27/30] SED-4539 Fix getCollection idempotence --- .../AutomationPackageCollectionFactory.java | 46 +++++++++++-------- ...utomationPackageCollectionFactoryTest.java | 20 ++++++++ 2 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java 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 index 8fc07916b4..ddffbe846f 100644 --- 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 @@ -18,19 +18,22 @@ ******************************************************************************/ 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; import step.functions.Function; import step.parameter.Parameter; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + public class AutomationPackageCollectionFactory implements CollectionFactory { private final InMemoryCollectionFactory baseFactory; private final AutomationPackageYamlFragmentManager fragmentManager; + private final Map> collectionsByName = new ConcurrentHashMap<>(); public AutomationPackageCollectionFactory(Properties properties, AutomationPackageYamlFragmentManager fragmentManager) { this.fragmentManager = fragmentManager; @@ -38,24 +41,27 @@ public AutomationPackageCollectionFactory(Properties properties, AutomationPacka } @Override - public Collection getCollection(String name, Class entityClass) { - - if (Plan.class.isAssignableFrom(entityClass)) { - return (Collection) new AutomationPackagePlanCollection(fragmentManager); - } else if (Parameter.class.isAssignableFrom(entityClass)) { - return (Collection) new AutomationPackageParameterCollection(fragmentManager); - } else if (Function.class.isAssignableFrom(entityClass)) { - return (Collection) new AutomationPackageFunctionCollection(fragmentManager); - } - - return baseFactory.getCollection(name, entityClass); - } + @SuppressWarnings("unchecked") + public Collection getCollection(String name, Class entityClass) { + return (Collection) collectionsByName.computeIfAbsent(name, (_name) -> { + if (Plan.class.isAssignableFrom(entityClass)) { + return new AutomationPackagePlanCollection(fragmentManager); + } else if (Parameter.class.isAssignableFrom(entityClass)) { + return new AutomationPackageParameterCollection(fragmentManager); + } else if (Function.class.isAssignableFrom(entityClass)) { + return new AutomationPackageFunctionCollection(fragmentManager); + } + return baseFactory.getCollection(name, entityClass); + }); + } - @Override - public Collection getVersionedCollection(String name) { - Collection baseCollection = baseFactory.getCollection(name, EntityVersion.class); - return baseCollection; - } + @SuppressWarnings("rawtypes") + @Override + public Collection getVersionedCollection(String name) { + // TODO: I'm pretty sure the previous implementation was incorrect. + // Fix this once we need it and know what the correct implementation is. + throw new UnsupportedOperationException(); + } @Override public void close() throws IOException { diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java new file mode 100644 index 0000000000..e9fbe009c5 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java @@ -0,0 +1,20 @@ +package step.core.collections; + +import org.junit.Assert; +import org.junit.Test; +import step.core.plans.Plan; +import step.plans.parser.yaml.YamlPlan; + +import java.util.Properties; + +public class AutomationPackageCollectionFactoryTest extends AutomationPackageCollectionTestBase { + + @Test + public void testCollectionIdempotence() { + AutomationPackageCollectionFactory cf = new AutomationPackageCollectionFactory(new Properties(), fragmentManager); + Collection c1 = cf.getCollection(YamlPlan.PLANS_ENTITY_NAME, Plan.class); + Assert.assertTrue(c1.estimatedCount() > 0); // just for good measure + Collection c2 = cf.getCollection(YamlPlan.PLANS_ENTITY_NAME, Plan.class); + Assert.assertSame(c1, c2); + } +} From f2f8efcfa131dda60a7e3c53a3311de8a9745c87 Mon Sep 17 00:00:00 2001 From: Cyril Misev Date: Wed, 6 May 2026 03:07:07 +0200 Subject: [PATCH 28/30] SED-4539 fix parameter editing --- .../packages/yaml/AutomationPackageYamlFragmentManager.java | 1 + 1 file changed, 1 insertion(+) 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 e2b0867c19..a3928cf8b9 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 @@ -291,6 +291,7 @@ public synchronized Date: Wed, 6 May 2026 03:16:48 +0200 Subject: [PATCH 29/30] SED-4539 improve parameter editing unit test --- .../collections/AutomationPackageParameterCollectionTest.java | 4 +++- .../expected/parametersAfterAddAndModification.yml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java index 15823837f4..0d8c50d5c1 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java @@ -75,7 +75,9 @@ public void testParameterAddAndModify() throws IOException { assertFilesEqual(expectedFilesPath.resolve("parametersAfterAdd.yml"), destinationDirectory.toPath().resolve("parameters.yml")); - parameter.setDescription("This is an added Parameter after modification"); + parameter.setDescription("This is an added Parameter with a new description"); + parameterCollection.save(parameter); + parameter.setValue(new DynamicValue<>("foo")); parameterCollection.save(parameter); diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml index 73c02cec44..5d87e49243 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml @@ -14,4 +14,4 @@ parameters: expression: "mySimpleKey" - key: "addedParameter" value: "foo" - description: "This is an added Parameter after modification" + description: "This is an added Parameter with a new description" From 72affeba0d1c6a7b3bd2288333119fdc4507c3b2 Mon Sep 17 00:00:00 2001 From: Christoph Langguth Date: Sat, 9 May 2026 12:16:31 +0200 Subject: [PATCH 30/30] SED-4678 refactor test classes/resources layout, rename module (#640) * SED-4663-fix-vulnerable-test-dependency-xmlunit-assertj (#637) SED-4663 Fix vulnerable xmlunit-assertj dependency * SED-4666 Small improvements to startup and shutdown (#638) * SED-000 fixing OS BE local test config * SED-4578 Refactor test classes/resources layout * SED-4678 Rename module * SED-4679 Fix automation-packages-controller/pom.xml (#641) * SED-4673 Improved errors for inexistent fragment references in AP (#642) * SED-4673 Graceful handling of inexistent fragment references in AP * SED-4673 Add another variant * SED-4673 AI PR feedbacks; consistent logger naming and usage * SED-4673 Updated according to discussion * SED-4681 Process automation-package.yaml once, not twice (#643) * SED-4681 Process automation-package.yaml once, not twice * SED-4681 Adjusting test file * SED-4681 Fixing Format --------- Co-authored-by: David Stephan --- step-automation-packages/pom.xml | 2 +- .../pom.xml | 9 +-- .../AutomationPackageCollectionFactory.java | 0 .../AutomationPackageFunctionCollection.java | 0 .../AutomationPackageParameterCollection.java | 0 .../AutomationPackagePlanCollection.java | 0 ...utomationPackageCollectionFactoryTest.java | 0 .../AutomationPackageCollectionTestBase.java | 20 ++--- ...utomationPackageKeywordCollectionTest.java | 6 +- ...omationPackageParameterCollectionTest.java | 7 ++ .../AutomationPackagePlanCollectionTest.java | 13 ++- ...utomationPackageWithEmptyWildcardTest.java | 17 ++++ ...ionPackageWithNonexistentFragmentTest.java | 31 +++++++ ...ionPackageWithNonexistentWildcardTest.java | 33 ++++++++ .../resources}/expected/Hello World Plan.yml | 0 .../expected/descriptorAfterAdd.yml | 3 + .../keywordsAfterCompositeModification.yml | 0 .../expected/parametersAfterAdd.yml | 0 .../parametersAfterAddAndModification.yml | 0 .../expected/parametersAfterModification.yml | 0 .../resources}/expected/plan1AfterAdd.yml | 0 .../expected/plan1AfterModification.yml | 0 .../expected/plan1AfterModifyAndAdd.yml | 0 .../resources}/expected/plan1AfterRemove.yml | 0 .../resources}/expected/plan1AfterRename.yml | 0 .../automation-package.yml | 5 ++ .../existingbutempty/README.txt | 1 + .../automation-package.yml | 5 ++ .../automation-package.yml | 5 ++ .../test/resources/testdata/ap1}/.apignore | 0 .../testdata/ap1}/automation-package.yml | 5 +- .../resources/testdata/ap1}/ignoredFile.yml | 0 .../ap1}/jmeterProject1/jmeterProject1.xml | 0 .../testdata/ap1}/jsProject/jsSample.js | 0 .../test/resources/testdata/ap1}/keywords.yml | 0 .../resources/testdata/ap1}/lib/fakeLib.jar | 0 .../testdata/ap1}/nodeProject/nodeSample.ts | 0 .../resources/testdata/ap1}/parameters.yml | 0 .../resources/testdata/ap1}/parameters2.yml | 0 .../test/resources/testdata/ap1}/plan.plan | 0 .../resources/testdata/ap1}/plans/plan1.yml | 0 .../resources/testdata/ap1}/plans/plan2.plan | 0 .../resources/testdata/ap1}/plans/plan2.yml | 0 .../ap1}/plansPlainText/firstPlainText.plan | 0 .../ap1}/plansPlainText/secondPlainText.plan | 0 .../resources/testdata/ap1}/schedules.yml | 0 .../test/resources/testdata/ap1}/unknown.yml | 0 .../pom.xml | 80 ++++++++++++++----- .../packages/AutomationPackageReader.java | 9 +-- .../packages/JavaAutomationPackageReader.java | 15 ++-- .../AutomationPackageYamlFragmentManager.java | 17 ++-- .../MeasurementControllerPlugin.java | 15 +++- step-controller/ControllerServer.run.xml | 17 ++-- .../layouts/presets/StepMainReportLayout.json | 18 ----- .../core/controller/StepControllerPlugin.java | 10 ++- .../ResourcePathMatchingResolver.java | 20 +++-- step-plans/step-plans-base-artefacts/pom.xml | 12 ++- .../reporting/JUnit4ReportWriterTest.java | 14 +++- .../JunitReportEntryCollectorTest.java | 2 +- 59 files changed, 277 insertions(+), 114 deletions(-) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/pom.xml (86%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java (100%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java (100%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/main/java/step/core/collections/AutomationPackageParameterCollection.java (100%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/main/java/step/core/collections/AutomationPackagePlanCollection.java (100%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java (100%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java (84%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java (94%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java (92%) rename step-automation-packages/{step-automation-packages-ide => step-automation-packages-collections}/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java (96%) create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithEmptyWildcardTest.java create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentFragmentTest.java create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentWildcardTest.java rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/Hello World Plan.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/descriptorAfterAdd.yml (91%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/keywordsAfterCompositeModification.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/parametersAfterAdd.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/parametersAfterAddAndModification.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/parametersAfterModification.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/plan1AfterAdd.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/plan1AfterModification.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/plan1AfterModifyAndAdd.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/plan1AfterRemove.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources}/expected/plan1AfterRename.yml (100%) create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/automation-package.yml create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/existingbutempty/README.txt create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-fragment/automation-package.yml create mode 100644 step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-wildcard/automation-package.yml rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/.apignore (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/automation-package.yml (88%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/ignoredFile.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/jmeterProject1/jmeterProject1.xml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/jsProject/jsSample.js (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/keywords.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/lib/fakeLib.jar (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/nodeProject/nodeSample.ts (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/parameters.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/parameters2.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plan.plan (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plans/plan1.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plans/plan2.plan (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plans/plan2.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plansPlainText/firstPlainText.plan (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/plansPlainText/secondPlainText.plan (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/schedules.yml (100%) rename step-automation-packages/{step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1 => step-automation-packages-collections/src/test/resources/testdata/ap1}/unknown.yml (100%) diff --git a/step-automation-packages/pom.xml b/step-automation-packages/pom.xml index c0af7bb484..3b9e7fbb26 100644 --- a/step-automation-packages/pom.xml +++ b/step-automation-packages/pom.xml @@ -42,7 +42,7 @@ step-automation-packages-manager step-automation-packages-client step-automation-packages-controller - step-automation-packages-ide + step-automation-packages-collections diff --git a/step-automation-packages/step-automation-packages-ide/pom.xml b/step-automation-packages/step-automation-packages-collections/pom.xml similarity index 86% rename from step-automation-packages/step-automation-packages-ide/pom.xml rename to step-automation-packages/step-automation-packages-collections/pom.xml index db6d0906c9..14f7950774 100644 --- a/step-automation-packages/step-automation-packages-ide/pom.xml +++ b/step-automation-packages/step-automation-packages-collections/pom.xml @@ -9,14 +9,7 @@ 0.0.0-SNAPSHOT - step-automation-packages-ide - - - ch.exense.step - 21 - 21 - UTF-8 - + step-automation-packages-collections 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-collections/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java rename to step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java rename to step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageParameterCollection.java similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageParameterCollection.java rename to step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageParameterCollection.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-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java rename to step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java rename to step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionFactoryTest.java diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java similarity index 84% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java rename to step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java index 86708a3741..3ed25186ed 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java @@ -22,42 +22,32 @@ import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; -import org.junit.Test; import org.mockito.Mockito; -import step.artefacts.Echo; -import step.artefacts.Sequence; import step.automation.packages.AutomationPackageHookRegistry; import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.JavaAutomationPackageReader; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.automation.packages.yaml.YamlAutomationPackageVersions; -import step.core.dynamicbeans.DynamicValue; -import step.core.plans.Plan; -import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; -import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; -import step.parameter.Parameter; import step.parameter.ParameterManager; import step.parameter.automation.AutomationPackageParametersRegistration; -import step.plans.parser.yaml.YamlPlan; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; public class AutomationPackageCollectionTestBase { private final JavaAutomationPackageReader reader; - protected final File sourceDirectory = new File("src/test/resources/samples/step-automation-packages-sample1"); + // To use a different source directory, override in subclass constructor + protected File sourceDirectory = new File("src/test/resources/testdata/ap1"); protected File destinationDirectory; - protected final Path expectedFilesPath = sourceDirectory.toPath().resolve("expected"); + protected Path expectedFilesPath = new File("src/test/resources/expected").toPath(); protected AutomationPackageYamlFragmentManager fragmentManager; public AutomationPackageCollectionTestBase() { diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java similarity index 94% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java rename to step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java index f1eae6e93e..5a52ed56b5 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java @@ -24,11 +24,8 @@ import step.automation.packages.AutomationPackageReadingException; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.core.accessors.AbstractOrganizableObject; -import step.core.artefacts.AbstractArtefact; import step.core.dynamicbeans.DynamicValue; -import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; import step.functions.Function; -import step.parameter.Parameter; import step.plugins.functions.types.CompositeFunction; import java.io.IOException; @@ -38,7 +35,8 @@ import java.util.Set; import java.util.stream.Collectors; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class AutomationPackageKeywordCollectionTest extends AutomationPackageCollectionTestBase { diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java similarity index 92% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java rename to step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java index 0d8c50d5c1..8c33637c55 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.Properties; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -84,4 +85,10 @@ public void testParameterAddAndModify() throws IOException { assertFilesEqual(expectedFilesPath.resolve("parametersAfterAddAndModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); } + @Test + public void testParametersInRootApAreAddedOnlyOnce() throws Exception { + // SED-4681 + assertEquals(1, parameterCollection.find(Filters.equals("key", "paramInMainAP"), null, null, null, 0).count()); + } + } diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java similarity index 96% rename from step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java rename to step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java index 5ebfb3bb3c..0abe4b2e61 100644 --- a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackagePlanCollectionTest.java @@ -31,10 +31,17 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; import java.util.stream.Collectors; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; public class AutomationPackagePlanCollectionTest extends AutomationPackageCollectionTestBase { @@ -52,7 +59,7 @@ public void setUp() throws IOException, AutomationPackageReadingException { } @Test - public void testReadAllPlans() { + public void testReadAllPlans() { long count = planCollection.count(Filters.empty(), 100); List plans = planCollection.find(Filters.empty(), null, null, null, 100).collect(Collectors.toList()); diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithEmptyWildcardTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithEmptyWildcardTest.java new file mode 100644 index 0000000000..3b87a0fab9 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithEmptyWildcardTest.java @@ -0,0 +1,17 @@ +package step.core.collections; + +import org.junit.Test; + +import java.io.File; + +public class AutomationPackageWithEmptyWildcardTest extends AutomationPackageCollectionTestBase { + + public AutomationPackageWithEmptyWildcardTest() { + super.sourceDirectory = new File("src/test/resources/testdata/ap-with-empty-wildcard"); + } + + @Test + public void testLoading() { + // we're happy if this package managed to load without throwing an exception + } +} diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentFragmentTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentFragmentTest.java new file mode 100644 index 0000000000..c9680d979e --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentFragmentTest.java @@ -0,0 +1,31 @@ +package step.core.collections; + +import ch.exense.commons.app.Configuration; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import step.automation.packages.AutomationPackageHookRegistry; +import step.automation.packages.JavaAutomationPackageReader; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.YamlAutomationPackageVersions; +import step.parameter.ParameterManager; +import step.parameter.automation.AutomationPackageParametersRegistration; + +import java.io.File; + +public class AutomationPackageWithNonexistentFragmentTest { + + @Test + public void testLoading() throws Exception { + AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); + AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); + AutomationPackageParametersRegistration.registerParametersHooks(hookRegistry, serializationRegistry, Mockito.mock(ParameterManager.class)); + var reader = new JavaAutomationPackageReader(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, hookRegistry, serializationRegistry, new Configuration()); + try { + reader.getAutomationPackageYamlFragmentManager(new File("src/test/resources/testdata/ap-with-nonexisting-fragment")); + Assert.fail("Expected exception"); + } catch (IllegalArgumentException e) { + Assert.assertEquals("Illegal resource definition, resource cannot be found: nonexisting.yml", e.getMessage()); + } + } +} diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentWildcardTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentWildcardTest.java new file mode 100644 index 0000000000..756878ef30 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageWithNonexistentWildcardTest.java @@ -0,0 +1,33 @@ +package step.core.collections; + +import ch.exense.commons.app.Configuration; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import step.automation.packages.AutomationPackageHookRegistry; +import step.automation.packages.AutomationPackageReadingException; +import step.automation.packages.JavaAutomationPackageReader; +import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; +import step.automation.packages.yaml.YamlAutomationPackageVersions; +import step.parameter.ParameterManager; +import step.parameter.automation.AutomationPackageParametersRegistration; + +import java.io.File; +import java.io.IOException; + +public class AutomationPackageWithNonexistentWildcardTest { + + @Test + public void testLoading() throws Exception { + AutomationPackageSerializationRegistry serializationRegistry = new AutomationPackageSerializationRegistry(); + AutomationPackageHookRegistry hookRegistry = new AutomationPackageHookRegistry(); + AutomationPackageParametersRegistration.registerParametersHooks(hookRegistry, serializationRegistry, Mockito.mock(ParameterManager.class)); + var reader = new JavaAutomationPackageReader(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, hookRegistry, serializationRegistry, new Configuration()); + try { + reader.getAutomationPackageYamlFragmentManager(new File("src/test/resources/testdata/ap-with-nonexisting-wildcard")); + Assert.fail("Expected exception"); + } catch (IllegalArgumentException e) { + Assert.assertEquals("Illegal resource definition, resource cannot be found: nonexisting/*.yml", e.getMessage()); + } + } +} 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-collections/src/test/resources/expected/Hello World Plan.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/Hello World Plan.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/Hello World Plan.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-collections/src/test/resources/expected/descriptorAfterAdd.yml similarity index 91% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/descriptorAfterAdd.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterAdd.yml index de6a8fdfed..9db0b00a0e 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-collections/src/test/resources/expected/descriptorAfterAdd.yml @@ -19,6 +19,9 @@ plans: children: - echo: text: "Hello World" +parameters: + - key: "paramInMainAP" + value: "once" fragments: - "keywords.yml" - "plans/*.yml" diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/keywordsAfterCompositeModification.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/keywordsAfterCompositeModification.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/keywordsAfterCompositeModification.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAdd.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAddAndModification.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterAddAndModification.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAddAndModification.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterModification.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/parametersAfterModification.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterModification.yml 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-collections/src/test/resources/expected/plan1AfterAdd.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterAdd.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/plan1AfterAdd.yml 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-collections/src/test/resources/expected/plan1AfterModification.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModification.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/plan1AfterModification.yml 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-collections/src/test/resources/expected/plan1AfterModifyAndAdd.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterModifyAndAdd.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/plan1AfterModifyAndAdd.yml 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-collections/src/test/resources/expected/plan1AfterRemove.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/plan1AfterRemove.yml 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-collections/src/test/resources/expected/plan1AfterRename.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRename.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/expected/plan1AfterRename.yml diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/automation-package.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/automation-package.yml new file mode 100644 index 0000000000..e02870463b --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/automation-package.yml @@ -0,0 +1,5 @@ +schemaVersion: 1.0.0 +name: "My package" +plans: [ ] +fragments: + - "existingbutempty/*.yml" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/existingbutempty/README.txt b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/existingbutempty/README.txt new file mode 100644 index 0000000000..5f778139c2 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-empty-wildcard/existingbutempty/README.txt @@ -0,0 +1 @@ +This directory exists, but no files matching existingbutempty/*.yml (referenced in the AP) exist. diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-fragment/automation-package.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-fragment/automation-package.yml new file mode 100644 index 0000000000..c25032a65e --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-fragment/automation-package.yml @@ -0,0 +1,5 @@ +schemaVersion: 1.0.0 +name: "My package" +plans: [ ] +fragments: + - "nonexisting.yml" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-wildcard/automation-package.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-wildcard/automation-package.yml new file mode 100644 index 0000000000..b0cd686950 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap-with-nonexisting-wildcard/automation-package.yml @@ -0,0 +1,5 @@ +schemaVersion: 1.0.0 +name: "My package" +plans: [ ] +fragments: + - "nonexisting/*.yml" 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-collections/src/test/resources/testdata/ap1/.apignore similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/.apignore rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/.apignore 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-collections/src/test/resources/testdata/ap1/automation-package.yml similarity index 88% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/automation-package.yml index c80fc11302..3238de01e0 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-collections/src/test/resources/testdata/ap1/automation-package.yml @@ -12,7 +12,10 @@ alertingRules: predicate: BindingValueEqualsPredicate: value: "myValue" -plans: [] +plans: [ ] +parameters: + - key: "paramInMainAP" + value: "once" fragments: - "keywords.yml" - "plans/*.yml" 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-collections/src/test/resources/testdata/ap1/ignoredFile.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/ignoredFile.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/ignoredFile.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/jmeterProject1/jmeterProject1.xml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jmeterProject1/jmeterProject1.xml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/jmeterProject1/jmeterProject1.xml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jsProject/jsSample.js b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/jsProject/jsSample.js similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/jsProject/jsSample.js rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/jsProject/jsSample.js 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-collections/src/test/resources/testdata/ap1/keywords.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/keywords.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/keywords.yml diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/lib/fakeLib.jar b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/lib/fakeLib.jar similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/lib/fakeLib.jar rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/lib/fakeLib.jar diff --git a/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/nodeProject/nodeSample.ts b/step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/nodeProject/nodeSample.ts similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/nodeProject/nodeSample.ts rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/nodeProject/nodeSample.ts 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-collections/src/test/resources/testdata/ap1/parameters.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/parameters.yml 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-collections/src/test/resources/testdata/ap1/parameters2.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/parameters2.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/parameters2.yml 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-collections/src/test/resources/testdata/ap1/plan.plan similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plan.plan rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plan.plan 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-collections/src/test/resources/testdata/ap1/plans/plan1.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan1.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plans/plan1.yml 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-collections/src/test/resources/testdata/ap1/plans/plan2.plan similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.plan rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plans/plan2.plan 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-collections/src/test/resources/testdata/ap1/plans/plan2.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plans/plan2.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plans/plan2.yml 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-collections/src/test/resources/testdata/ap1/plansPlainText/firstPlainText.plan similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/firstPlainText.plan rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plansPlainText/firstPlainText.plan 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-collections/src/test/resources/testdata/ap1/plansPlainText/secondPlainText.plan similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/plansPlainText/secondPlainText.plan rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/plansPlainText/secondPlainText.plan 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-collections/src/test/resources/testdata/ap1/schedules.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/schedules.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/schedules.yml 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-collections/src/test/resources/testdata/ap1/unknown.yml similarity index 100% rename from step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/unknown.yml rename to step-automation-packages/step-automation-packages-collections/src/test/resources/testdata/ap1/unknown.yml diff --git a/step-automation-packages/step-automation-packages-controller/pom.xml b/step-automation-packages/step-automation-packages-controller/pom.xml index f6262fb66b..c5f644160d 100644 --- a/step-automation-packages/step-automation-packages-controller/pom.xml +++ b/step-automation-packages/step-automation-packages-controller/pom.xml @@ -28,7 +28,18 @@ ${project.version} - + + + org.mockito + mockito-core + test + + + org.awaitility + awaitility + 4.3.0 + test + ch.exense.step step-functions-plugins-defs-all-os @@ -43,16 +54,51 @@ test - org.mockito - mockito-core + ch.exense.step + step-automation-packages-sample1 + ${project.version} + jar-with-dependencies test - org.awaitility - awaitility - 4.3.0 + ch.exense.step + step-automation-packages-sample1-extended + ${project.version} + jar-with-dependencies + test + + + ch.exense.step + step-automation-packages-sample2 + ${project.version} + jar-with-dependencies + test + + + ch.exense.step + step-automation-packages-malformed + ${project.version} + test + + + ch.exense.step + step-automation-packages-kw-lib + ${project.version} + jar-with-dependencies + test + + + ch.exense.step + step-automation-packages-sample-echo + ${project.version} + test + + + ch.exense.step + step-automation-packages-kw-lib-call + ${project.version} + jar-with-dependencies test - jar @@ -75,7 +121,7 @@ copy-test - validate + process-test-resources copy @@ -98,7 +144,7 @@ copy-test-2 - validate + process-test-resources copy @@ -121,7 +167,7 @@ copy-test-3 - validate + process-test-resources copy @@ -144,7 +190,7 @@ copy-test-4 - validate + process-test-resources copy @@ -154,7 +200,6 @@ ch.exense.step step-automation-packages-malformed ${project.version} - jar false ${project.basedir}/src/test/resources/samples @@ -167,7 +212,7 @@ copy-test-5 - validate + process-test-resources copy @@ -190,7 +235,7 @@ copy-test-6 - validate + process-test-resources copy @@ -213,7 +258,7 @@ copy-test-7 - validate + process-test-resources copy @@ -236,7 +281,7 @@ copy-test-8 - validate + process-test-resources copy @@ -246,7 +291,6 @@ ch.exense.step step-automation-packages-sample-echo ${project.version} - jar false ${project.basedir}/src/test/resources/samples @@ -259,7 +303,7 @@ copy-test-9 - validate + process-test-resources copy 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 a6e8ba6c49..73171c5cd5 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 @@ -59,9 +59,8 @@ * these resources are not stored yet). */ public abstract class AutomationPackageReader { - + private static final Logger logger = LoggerFactory.getLogger(AutomationPackageReader.class); public static final String AP_VERSION_SEPARATOR = "."; - protected static final Logger log = LoggerFactory.getLogger(AutomationPackageReader.class); private final PlanParser planTextPlanParser; protected String jsonSchemaPath; protected final AutomationPackageHookRegistry hookRegistry; @@ -185,7 +184,7 @@ protected AutomationPackageContent newContentInstance() { public AutomationPackageYamlFragmentManager getAutomationPackageYamlFragmentManager(T archive) throws AutomationPackageReadingException { AutomationPackageDescriptorReader reader = getOrCreateDescriptorReader(); URL descriptorURL = archive.getDescriptorYamlUrl(); - try (InputStream inputStream = descriptorURL.openStream()){ + try (InputStream inputStream = descriptorURL.openStream()) { AutomationPackageDescriptorYaml descriptor = reader.readAutomationPackageDescriptor(inputStream, archive.getOriginalFileName()); descriptor.setFragmentUrl(descriptorURL); AutomationPackageContent content = newContentInstance(); @@ -227,7 +226,7 @@ protected void fillContentSections(AutomationPackageContent targetPackage, Autom for (Map.Entry> additionalField : fragment.getAdditionalFields().entrySet()) { boolean hooked = hookRegistry.onAdditionalDataRead(additionalField.getKey(), additionalField.getValue(), targetPackage); if (!hooked) { - log.warn("Hook not found for additional field " + additionalField.getKey() + ". The additional field has been skipped"); + logger.warn("Hook not found for additional field " + additionalField.getKey() + ". The additional field has been skipped"); } } } @@ -296,7 +295,7 @@ protected synchronized AutomationPackageDescriptorReader getOrCreateDescriptorRe } public synchronized void updateJsonSchema(String jsonSchemaPath) { - log.info("Change json schema for automation package to {}", jsonSchemaPath); + logger.info("Change json schema for automation package to {}", jsonSchemaPath); this.jsonSchemaPath = jsonSchemaPath; this.descriptorReader = null; } 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 11095780a8..f1fafc7a22 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 @@ -2,6 +2,8 @@ import ch.exense.commons.app.Configuration; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.ScriptAutomationPackageKeyword; import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; @@ -35,6 +37,7 @@ import java.util.Set; public class JavaAutomationPackageReader extends AutomationPackageReader { + private static final Logger logger = LoggerFactory.getLogger(JavaAutomationPackageReader.class); protected final StepClassParser stepClassParser; @@ -65,13 +68,13 @@ protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(JavaAutomation // instead of this we keep the scriptFile blank and fill it further in AutomationPackageKeywordsAttributesApplier (after we upload the jar file as resource) List scannedKeywords = extractAnnotatedKeywords(annotationScanner, null, null); if (!scannedKeywords.isEmpty()) { - log.info("{} annotated keywords found in automation package {}", scannedKeywords.size(), StringUtils.defaultString(archive.getAutomationPackageName())); + logger.info("{} annotated keywords found in automation package {}", scannedKeywords.size(), StringUtils.defaultString(archive.getAutomationPackageName())); } res.getKeywords().addAll(scannedKeywords); List annotatedPlans = extractAnnotatedPlans(archive, annotationScanner, stepClassParser); if (!annotatedPlans.isEmpty()) { - log.info("{} annotated plans found in automation package {}", annotatedPlans.size(), StringUtils.defaultString(archive.getAutomationPackageName())); + logger.info("{} annotated plans found in automation package {}", annotatedPlans.size(), StringUtils.defaultString(archive.getAutomationPackageName())); } res.getPlans().addAll(annotatedPlans); } catch (JsonSchemaPreparationException e) { @@ -157,7 +160,7 @@ private static List getPlanFromPlansAnnotation(Annotation try { ((AutoCloseable) classLoader).close(); } catch (Exception e) { - log.error("Unable to close the classloader created from provided package file '{}' after reading its content.", archive.getOriginalFile().getName()); + logger.error("Unable to close the classloader created from provided package file '{}' after reading its content.", archive.getOriginalFile().getName()); } } } @@ -176,7 +179,7 @@ private static List extractAnnotatedKeywords(Ann for (Method m : methods) { Keyword annotation = m.getAnnotation(Keyword.class); if (annotation == null) { - log.warn("Keyword annotation is not found for method " + m.getName()); + logger.warn("Keyword annotation is not found for method " + m.getName()); continue; } @@ -238,7 +241,9 @@ private static boolean isCompositeFunction(Keyword annotation) { return annotation.planReference() != null && !annotation.planReference().isBlank(); } - /** Reads automation package into a yaml fragment manager + /** + * Reads automation package into a yaml fragment manager + * * @param automationPackage the JAR file to be read * @return the automation package fragment manager read from the provided files for editing * @throws AutomationPackageReadingException in case of error 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 a3928cf8b9..5f05a50670 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,7 +18,6 @@ ******************************************************************************/ package step.automation.packages.yaml; -import step.automation.packages.AutomationPackageOperationMode; import step.automation.packages.StagingAutomationPackageContext; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; @@ -36,8 +35,6 @@ import step.parameter.Parameter; import step.parameter.automation.AutomationPackageParameter; import step.plans.parser.yaml.YamlPlan; -import step.resources.LocalResourceManagerImpl; -import step.resources.ResourceManager; import java.io.File; import java.net.MalformedURLException; @@ -46,7 +43,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.text.MessageFormat; -import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -92,7 +88,9 @@ public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml desc this.stagingContext = stagingContext; initializeMaps(descriptorYaml); - pathToYamlFragment.values().forEach(this::initializeMaps); + pathToYamlFragment.values().stream() + .filter(f -> f != descriptorYaml) + .forEach(this::initializeMaps); } public void setProperties(Properties properties) { @@ -101,7 +99,7 @@ public void setProperties(Properties properties) { public void initializeMaps(AutomationPackageFragmentYaml fragment) { pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - for (YamlPlan yamlPlan: fragment.getPlans()) { + for (YamlPlan yamlPlan : fragment.getPlans()) { Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(yamlPlan); patchableMap.put(plan, yamlPlan); fragmentMap.put(plan, fragment); @@ -157,7 +155,6 @@ public synchronized Plan savePlan(Plan plan) { } - public synchronized step.functions.Function saveFunction(step.functions.Function function) { AutomationPackageFragmentYaml fragment = fragmentMap.get(function); if (fragment == null) { @@ -175,7 +172,7 @@ public synchronized step.functions.Function saveFunction(step.functions.Function return function; } - private void addFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T newEntity) { + private void addFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T newEntity) { entityList.add(newEntity); fragment.writeToDisk(); } @@ -194,7 +191,7 @@ private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableOb } - if (mode== NewObjectFragmentMode.PER_OBJECT && !p.hasAttribute(AbstractOrganizableObject.NAME)) { + if (mode == NewObjectFragmentMode.PER_OBJECT && !p.hasAttribute(AbstractOrganizableObject.NAME)) { throw new AutomationPackagePerObjectSaveUnsupportedException(String.format(""" Saving by object name is unsupported for %1$s, please configure the entity to be stored in a specified single fragment, i.e. @@ -221,7 +218,7 @@ private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableOb return pathToYamlFragment.get(url.toString()); } PatchingContext context = new PatchingContext("---", descriptorYaml.getPatchingContext().getMapper()); - AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); + AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); fragment.setFragmentUrl(url); return fragment; } catch (MalformedURLException e) { diff --git a/step-controller-plugins/step-controller-plugins-measurement/src/main/java/step/plugins/measurements/MeasurementControllerPlugin.java b/step-controller-plugins/step-controller-plugins-measurement/src/main/java/step/plugins/measurements/MeasurementControllerPlugin.java index 8e3b7399cf..e5dfaf2607 100644 --- a/step-controller-plugins/step-controller-plugins-measurement/src/main/java/step/plugins/measurements/MeasurementControllerPlugin.java +++ b/step-controller-plugins/step-controller-plugins-measurement/src/main/java/step/plugins/measurements/MeasurementControllerPlugin.java @@ -17,12 +17,19 @@ import step.grid.TokenWrapperState; import step.grid.reports.TokenGroupCapacity; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; -import static step.plugins.measurements.MeasurementPlugin.*; +import static step.plugins.measurements.MeasurementPlugin.MEASURE_FIELDS; +import static step.plugins.measurements.MeasurementPlugin.MEASURE_NOT_DATA_KEYS; +import static step.plugins.measurements.MeasurementPlugin.VALUE; @Plugin(dependencies = {GridPlugin.class}) public class MeasurementControllerPlugin extends AbstractControllerPlugin { @@ -139,7 +146,9 @@ public List collect() { @Override public void serverStop(GlobalContext context) { - gaugeCollectorRegistry.stop(); + if (gaugeCollectorRegistry != null) { + gaugeCollectorRegistry.stop(); + } } @Override diff --git a/step-controller/ControllerServer.run.xml b/step-controller/ControllerServer.run.xml index d63e62d22b..31f632ed5a 100644 --- a/step-controller/ControllerServer.run.xml +++ b/step-controller/ControllerServer.run.xml @@ -1,18 +1,19 @@ - diff --git a/step-controller/step-controller-backend/src/test/resources/reporting/layouts/presets/StepMainReportLayout.json b/step-controller/step-controller-backend/src/test/resources/reporting/layouts/presets/StepMainReportLayout.json index c4ed57d7b4..3fb0ba69e6 100644 --- a/step-controller/step-controller-backend/src/test/resources/reporting/layouts/presets/StepMainReportLayout.json +++ b/step-controller/step-controller-backend/src/test/resources/reporting/layouts/presets/StepMainReportLayout.json @@ -75,24 +75,6 @@ "heightInCells": 3 } }, - { - "widgetType": "notificationSubscriptionForExecution", - "position": { - "row": 14, - "column": 1, - "widthInCells": 4, - "heightInCells": 3 - } - }, - { - "widgetType": "housekeepingSettingsForExecution", - "position": { - "row": 14, - "column": 5, - "widthInCells": 4, - "heightInCells": 3 - } - }, { "widgetType": "currentOperations", "position": { diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/StepControllerPlugin.java b/step-controller/step-controller-server/src/main/java/step/core/controller/StepControllerPlugin.java index 2c09d08684..c192485435 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/StepControllerPlugin.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/StepControllerPlugin.java @@ -13,8 +13,8 @@ import step.core.execution.model.ExecutionAccessor; import step.core.execution.model.ExecutionStatus; import step.core.plugins.AbstractControllerPlugin; -import step.core.plugins.exceptions.PluginCriticalException; import step.core.plugins.Plugin; +import step.core.plugins.exceptions.PluginCriticalException; import step.core.scheduler.ExecutionScheduler; import step.core.scheduler.SchedulerServices; import step.framework.server.CORSRequestResponseFilter; @@ -33,9 +33,13 @@ public class StepControllerPlugin extends AbstractControllerPlugin { private Controller controller; @Override - public void init(GlobalContext context) throws Exception { - // Only used for validation here, blows up if there is something wrong with the configuration. + public void bootstrapAndValidate(GlobalContext context) throws Exception { + // Only used for validation here (as early as possible), blows up if there is something wrong with the configuration. getControllerUrl(context.getConfiguration(), false, true); + } + + @Override + public void init(GlobalContext context) throws Exception { controller = new Controller(context); controller.init(context.getServiceRegistrationCallback()); context.put(Controller.class, controller); diff --git a/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java b/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java index 75765b5b0b..19553ff896 100644 --- a/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java +++ b/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java @@ -21,8 +21,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.*; -import java.util.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Pattern; public class ResourcePathMatchingResolver { @@ -37,11 +38,16 @@ public ResourcePathMatchingResolver(ClassLoader classLoader) { public List getResourcesByPattern(String resourcePathPattern) { List res = new ArrayList<>(); if (!containsWildcard(resourcePathPattern)) { - res.add(classLoader.getResource(resourcePathPattern)); + URL url = classLoader.getResource(resourcePathPattern); + if (url != null) { + res.add(url); + } else { + throw new IllegalArgumentException("Illegal resource definition, resource cannot be found: " + resourcePathPattern); + } } else { for (URL resource : findPathMatchingResources(resourcePathPattern)) { if (logger.isDebugEnabled()) { - logger.debug("Obtain resource from automation package: {}", resource); + logger.debug("Obtained resource from automation package: {}", resource); } res.add(resource); } @@ -62,7 +68,11 @@ protected List findPathMatchingResources(String locationPattern) { throw new RuntimeException("Wildcards are currently not supported for the root element of the path: " + rootPath + ". You should put all the fragments into a folder and reference them as follow: myFolder/*"); } else { URL resource = classLoader.getResource(rootPath); - findPathMatchingResourcesRecursive(pathArray, 0, resource, result); + if (resource != null) { + findPathMatchingResourcesRecursive(pathArray, 0, resource, result); + } else { + throw new IllegalArgumentException("Illegal resource definition, resource cannot be found: " + locationPattern); + } } return result; } diff --git a/step-plans/step-plans-base-artefacts/pom.xml b/step-plans/step-plans-base-artefacts/pom.xml index c331f867ca..2e20ec71b4 100644 --- a/step-plans/step-plans-base-artefacts/pom.xml +++ b/step-plans/step-plans-base-artefacts/pom.xml @@ -134,13 +134,19 @@ org.xmlunit xmlunit-core - 2.10.0 + 2.11.0 + test + + + org.assertj + assertj-core + 3.27.7 test org.xmlunit - xmlunit-assertj - 2.10.0 + xmlunit-assertj3 + 2.11.0 test diff --git a/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JUnit4ReportWriterTest.java b/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JUnit4ReportWriterTest.java index 833c8aff69..78713bec90 100644 --- a/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JUnit4ReportWriterTest.java +++ b/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JUnit4ReportWriterTest.java @@ -23,8 +23,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; -import org.xmlunit.assertj.XmlAssert; -import step.artefacts.*; +import org.xmlunit.assertj3.XmlAssert; +import step.artefacts.BaseArtefactPlugin; +import step.artefacts.ForBlock; +import step.artefacts.Sequence; +import step.artefacts.TestCase; +import step.artefacts.TestSet; import step.artefacts.handlers.functions.TokenForecastingExecutionPlugin; import step.core.artefacts.CheckArtefact; import step.core.artefacts.reports.ReportNode; @@ -45,7 +49,11 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.nio.file.Files; import java.time.ZoneId; import java.util.List; diff --git a/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JunitReportEntryCollectorTest.java b/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JunitReportEntryCollectorTest.java index a8698d8d6d..8f10ffa9c6 100644 --- a/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JunitReportEntryCollectorTest.java +++ b/step-plans/step-plans-base-artefacts/src/test/java/step/reporting/JunitReportEntryCollectorTest.java @@ -3,7 +3,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xmlunit.assertj.XmlAssert; +import org.xmlunit.assertj3.XmlAssert; public class JunitReportEntryCollectorTest {