diff --git a/step-automation-packages/pom.xml b/step-automation-packages/pom.xml index a5210c3099..c0af7bb484 100644 --- a/step-automation-packages/pom.xml +++ b/step-automation-packages/pom.xml @@ -42,9 +42,11 @@ step-automation-packages-manager step-automation-packages-client step-automation-packages-controller + step-automation-packages-ide + diff --git a/step-automation-packages/step-automation-packages-ide/pom.xml b/step-automation-packages/step-automation-packages-ide/pom.xml new file mode 100644 index 0000000000..db6d0906c9 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + ch.exense.step + step-automation-packages + 0.0.0-SNAPSHOT + + + step-automation-packages-ide + + + ch.exense.step + 21 + 21 + UTF-8 + + + + + ch.exense.step + step-plans-base-artefacts + ${project.version} + + + ch.exense.step + step-automation-packages-yaml + ${project.version} + + + + + org.mockito + mockito-core + test + + + ch.exense.step + step-plans-core + ${project.version} + test + + + ch.exense.step + step-functions-plugins-jmeter-def + ${project.version} + test + + + ch.exense.step + step-functions-plugins-node-def + ${project.version} + test + + + ch.exense.step + step-automation-packages-controller + ${project.version} + test + + + + \ No newline at end of file diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java new file mode 100644 index 0000000000..e3be980294 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackageCollectionFactory.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (C) 2026, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.collections; + +import java.io.IOException; +import java.util.Properties; + +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; +import step.core.collections.inmemory.InMemoryCollectionFactory; +import step.core.plans.Plan; + +public class AutomationPackageCollectionFactory implements CollectionFactory { + + private final InMemoryCollectionFactory baseFactory; + private final AutomationPackageYamlFragmentManager fragmentManager; + + public AutomationPackageCollectionFactory(Properties properties, AutomationPackageYamlFragmentManager fragmentManager) { + this.fragmentManager = fragmentManager; + this.baseFactory = new InMemoryCollectionFactory(properties); + } + + @Override + public Collection getCollection(String name, Class entityClass) { + + if (entityClass == Plan.class) { + return (Collection) new AutomationPackagePlanCollection(fragmentManager); + } + + return baseFactory.getCollection(name, entityClass); + } + + @Override + public Collection getVersionedCollection(String name) { + Collection baseCollection = baseFactory.getCollection(name, EntityVersion.class); + return baseCollection; + } + + @Override + public void close() throws IOException { + baseFactory.close(); + } +} diff --git a/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java b/step-automation-packages/step-automation-packages-ide/src/main/java/step/core/collections/AutomationPackagePlanCollection.java new file mode 100644 index 0000000000..4ba860257f --- /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, "plans"); + this.fragmentManager = fragmentManager; + fragmentManager.getPlans().forEach(super::save); + } + + @Override + public Plan save(Plan p){ + return super.save(fragmentManager.savePlan(p)); + } + + @Override + public void save(Iterable iterable) { + for (Plan p : iterable) { + save(p); + } + } + + @Override + public void remove(Filter filter) { + find(filter, null, null, null, Integer.MAX_VALUE).forEach(fragmentManager::removePlan); + super.remove(filter); + } +} diff --git a/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java new file mode 100644 index 0000000000..df3a96df65 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/java/step/core/collections/AutomationPackageCollectionTest.java @@ -0,0 +1,303 @@ +/******************************************************************************* + * 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.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.parameter.ParameterManager; +import step.parameter.automation.AutomationPackageParametersRegistration; + +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.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +public class AutomationPackageCollectionTest { + + 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"); + 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()); + } + + @Before + public void setUp() throws IOException, AutomationPackageReadingException { + Properties properties = new Properties(); + destinationDirectory = Files.createTempDirectory("automationPackageCollectionTest").toFile(); + FileUtils.copyDirectory(sourceDirectory, destinationDirectory); + + 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 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(); + + 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")); + } + + @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 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")); + 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")); + + planCollection.remove(Filters.equals("attributes.name", "New Name")); + + + assertFilesEqual(expectedFilesPath.resolve("plan1AfterModification.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 { + 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/.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..c80fc11302 --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/automation-package.yml @@ -0,0 +1,22 @@ +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" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.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..ac6f041ffe --- /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" +plans: +- version: "1.2.0" + name: "New Name" + root: + sequence: + children: + - echo: + text: "Hello World" + agents: null + categories: null +fragments: + - "keywords.yml" + - "plans/*.yml" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.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 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/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/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 new file mode 100644 index 0000000000..f01060a03f --- /dev/null +++ b/step-automation-packages/step-automation-packages-ide/src/test/resources/samples/step-automation-packages-sample1/expected/plan1AfterRemove.yml @@ -0,0 +1,9 @@ +--- +fragments: [] +keywords: [] +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..26e6c014f5 --- /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" 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 c0a1ad4b0b..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 @@ -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; @@ -122,7 +123,8 @@ protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescr // apply imported fragments recursively if (descriptor != null) { - fillAutomationPackageWithImportedFragments(res, descriptor, archive); + Map fragmentMap = new HashMap<>(); + fillAutomationPackageWithImportedFragments(res, descriptor, archive, fragmentMap); } return res; } @@ -173,7 +175,22 @@ protected AutomationPackageContent newContentInstance() { abstract protected void fillAutomationPackageWithAnnotatedKeywordsAndPlans(T archive, AutomationPackageContent res) throws AutomationPackageReadingException; - public void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive) throws AutomationPackageReadingException { + public AutomationPackageYamlFragmentManager provideAutomationPackageYamlFragmentManager(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); + return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader()); + } catch (IOException e) { + throw new AutomationPackageReadingException("Failed to read automation package for editing", e); + } + } + + private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive, Map fragmentYamlMap) throws AutomationPackageReadingException { fillContentSections(targetPackage, fragment, archive); if (!fragment.getFragments().isEmpty()) { @@ -182,7 +199,9 @@ public void fillAutomationPackageWithImportedFragments(AutomationPackageContent for (URL resource : resources) { try (InputStream fragmentYamlStream = resource.openStream()) { fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, importedFragmentReference, archive.getAutomationPackageName()); - fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive); + fragmentYamlMap.put(resource.toString(), fragment); + fragment.setFragmentUrl(resource); + fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive, fragmentYamlMap); } catch (IOException e) { throw new AutomationPackageReadingException("Unable to read fragment in automation package: " + importedFragmentReference, 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 f8898bd7b8..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 @@ -4,11 +4,11 @@ import org.apache.commons.lang3.StringUtils; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.model.ScriptAutomationPackageKeyword; +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; import step.core.accessors.AbstractOrganizableObject; import step.core.dynamicbeans.DynamicValue; import step.core.plans.Plan; import step.core.scanner.AnnotationScanner; -import step.engine.plugins.LocalFunctionPlugin; import step.functions.Function; import step.functions.manager.FunctionManagerImpl; import step.handlers.javahandler.Keyword; @@ -22,7 +22,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; @@ -241,7 +244,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 { @@ -251,4 +254,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 read from the provided files for editing + * @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-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/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/AutomationPackageDescriptorReader.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java index 5cd4764660..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,8 +18,11 @@ ******************************************************************************/ package step.automation.packages.yaml; -import com.fasterxml.jackson.databind.ObjectMapper; +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; @@ -29,13 +32,15 @@ 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.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.deserializers.StepYamlDeserializersScanner; +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; @@ -105,8 +110,10 @@ protected T readAutomationPackageYamlF } } - T res = yamlObjectMapper.reader().withAttribute("version", version).readValue(yamlDescriptorString, targetClass); - + 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); return res; } catch (IOException | YamlPlanValidationException e) { @@ -143,32 +150,54 @@ protected String readJsonSchema(String jsonSchemaPath) { } } - protected 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); 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(); - - // register deserializers to read yaml plans - planReader.registerAllSerializersAndDeserializers(module, yamlMapper, true); + 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); + } - // add annotated jackson deserializers - StepYamlDeserializersScanner.addAllDeserializerAddonsToModule(module, yamlMapper, List.of(stepYamlDeserializer -> { - if (stepYamlDeserializer instanceof AutomationPackageSerializationRegistryAware) { - ((AutomationPackageSerializationRegistryAware) stepYamlDeserializer).setSerializationRegistry(serializationRegistry); + @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); yamlMapper.registerModule(module); 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/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 new file mode 100644 index 0000000000..15c0fe9c49 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * 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.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; +import step.core.accessors.AbstractOrganizableObject; +import step.core.plans.Plan; +import step.core.yaml.PatchableYamlList; +import step.core.yaml.PatchableYamlModel; +import step.plans.parser.yaml.YamlPlan; + +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.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +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 pathToYamlFragment; + private Properties properties = new Properties(); + private final AutomationPackageFragmentYaml descriptorYaml; + + public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, Map fragmentMap, AutomationPackageDescriptorReader descriptorReader) { + + this.descriptorReader = descriptorReader; + this.descriptorYaml = descriptorYaml; + + pathToYamlFragment = fragmentMap; + + initializeMaps(descriptorYaml); + + pathToYamlFragment.values().forEach(this::initializeMaps); + } + + public void setProperties(Properties properties) { + this.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); + planToYamlFragment.put(plan, fragment); + } + } + + 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 = fragmentForNewPlan(p); + planToYamlFragment.put(p, fragment); + pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); + addFragmentEntity(fragment, YamlPlan.PLANS_ENTITY_NAME, 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, 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 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()); + }); + } + + 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))); + + Path path = new File(planFragmentPath).toPath(); + if (!path.isAbsolute()) { + Path apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) + .getParent(); + path = apRoot.resolve(path); + } + + try { + URL url = path.toUri().toURL(); + + + if (pathToYamlFragment.containsKey(url.toString())) return pathToYamlFragment.get(url.toString()); + 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); + } + + } + + public String sanitizeFilename(String inputName) { + return inputName.replaceAll("[^a-zA-Z0-9-_.]", "_"); + } + + 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); + + 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); + } + } +} 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..78b600cc6c --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocatedJsonNode.java @@ -0,0 +1,43 @@ +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 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..f2c0648325 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/LocationAwareTreeTraversingParser.java @@ -0,0 +1,22 @@ +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 sourceNode.getStartLocation(); + } +} 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..790ce21f2e --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlListDeserializer.java @@ -0,0 +1,49 @@ +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.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; + +public class PatchableYamlListDeserializer extends CollectionDeserializer { + + public PatchableYamlListDeserializer(CollectionDeserializer delegate) { + super(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()); + 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/PatchableYamlModelDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java new file mode 100644 index 0000000000..615816b748 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchableYamlModelDeserializer.java @@ -0,0 +1,56 @@ +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 com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import step.core.yaml.PatchableYamlModel; + +import java.io.IOException; + +public class PatchableYamlModelDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private final JsonDeserializer delegate; + + public PatchableYamlModelDeserializer(JsonDeserializer delegate) { + this.delegate = (JsonDeserializer) delegate; + } + + @Override + 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()); + + return entity; + } + return delegate.deserialize(p, ctxt); + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException { + JsonDeserializer contextual = delegate; + 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 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 new file mode 100644 index 0000000000..3b6f1951c8 --- /dev/null +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/PatchingParserDelegate.java @@ -0,0 +1,58 @@ +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 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; + +public class PatchingParserDelegate extends JsonParserDelegate { + + private final Map distinctLocationBeforeToken = new HashMap<>(); + + private JsonLocation lastDistinctLocation; + + private final Deque nodeStack = new ArrayDeque<>(); + + public PatchingParserDelegate(JsonParser d) { + super(d); + } + + @Override + public JsonToken nextToken() throws IOException { + JsonLocation preLocation = currentLocation(); + JsonToken token = super.nextToken(); + 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; + } + + protected JsonLocation getDistinctLocationBeforeToken(JsonToken token) { + return distinctLocationBeforeToken.get(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/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 3e9441978d..0000000000 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/YamlAutomationPackageFragmentDeserializer.java +++ /dev/null @@ -1,97 +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.core.ObjectCodec; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.node.ObjectNode; -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 step.core.yaml.SerializationUtils; - -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()); - 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(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; - } - - } - - 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 022ce163b5..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 @@ -18,25 +18,45 @@ ******************************************************************************/ package step.automation.packages.yaml.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -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.core.yaml.PatchableYamlList; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +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 PatchableYamlList plans = new PatchableYamlList<>(); private List plansPlainText = new ArrayList<>(); + 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; + @JsonIgnore - private Map> additionalFields; + private String currentYaml; @Override public List getKeywords() { @@ -44,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; } @@ -68,13 +88,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 @@ -86,4 +115,24 @@ 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 void setCurrentYaml(String yaml) { + this.currentYaml = yaml; + } + + @JsonIgnore + public String getCurrentYaml() { + return currentYaml; + } } 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 a141f5e090..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,10 +18,16 @@ ******************************************************************************/ 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; import java.util.Map; @@ -29,7 +35,7 @@ public interface AutomationPackageFragmentYaml { List getKeywords(); - List getPlans(); + PatchableYamlList getPlans(); List getPlansPlainText(); @@ -40,4 +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-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();} } 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..99cbb3356b --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlList.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 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-core/src/main/java/step/automation/packages/AutomationPackageArchive.java b/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java index c21da6031a..07268c8bfc 100644 --- a/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java +++ b/step-core/src/main/java/step/automation/packages/AutomationPackageArchive.java @@ -61,6 +61,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 41eb5f0091..10d97d6e2d 100644 --- a/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java +++ b/step-core/src/main/java/step/automation/packages/JavaAutomationPackageArchive.java @@ -122,6 +122,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-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-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 782c1ade15..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,6 +18,7 @@ ******************************************************************************/ package step.plans.parser.yaml; +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 +28,7 @@ import java.util.List; -public class YamlPlan { +public class YamlPlan extends PatchableAbstractYamlModel { public static final String PLANS_ENTITY_NAME = "plans"; @@ -81,7 +82,7 @@ public List getCategories() { return categories; } - public void setCategories(List categories) { - this.categories = categories; - } + public void setCategories(List categories) { + this.categories = categories; + } } 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..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,11 +18,14 @@ ******************************************************************************/ package step.plans.automation; +import step.core.yaml.AbstractYamlModel; +import step.core.yaml.PatchableAbstractYamlModel; import step.plans.nl.RootArtefactType; +import step.core.yaml.PatchableYamlModel; import java.util.List; -public class YamlPlainTextPlan { +public class YamlPlainTextPlan extends PatchableAbstractYamlModel { private String 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 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());