diff --git a/step-ap-ide/src/main/java/step/ap_ide/StepUp.java b/step-ap-ide/src/main/java/step/ap_ide/StepUp.java index 938961b7f2..709d01912d 100644 --- a/step-ap-ide/src/main/java/step/ap_ide/StepUp.java +++ b/step-ap-ide/src/main/java/step/ap_ide/StepUp.java @@ -21,7 +21,6 @@ import java.io.InputStream; import java.io.PrintWriter; import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Objects; import java.util.Properties; @@ -40,7 +39,7 @@ public static void main(String[] args) throws Exception { configuration.getUnderlyingPropertyObject().load(propsStream); new ControllerServer(configuration).start(); initWorkdir(); - FXApp.main(args); // this will never return + //FXApp.main(args); // this will never return*/ } private static final JavaAutomationPackageReader READER; @@ -79,18 +78,18 @@ static void useAutomationPackageDirectory(File apDir) throws Exception { var fragmentManager = StepUp.READER.getAutomationPackageYamlFragmentManager(apDir); Properties properties = new Properties(); + int variant = 1; // variant 1: // parameters all go into parameters.yml, plans go into separate files in plans/$PLAN_NAME.yml - // Only works if the target files/directories already exist, so disabled for now - if (1 == 0) { + // this is because parameters do not have unique names by design. + if (variant == 1) { properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, Parameter.ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, Parameter.ENTITY_NAME), "parameters.yml"); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, YamlPlan.PLANS_ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.PER_OBJECT.name()); - // keywords seem to use PER_OBJECT by default? + // per default, PER_OBJECT is used on all objects? } // variant 2: simple, everything goes into main descriptor - if (1 == 1) { - String mainFile = Paths.get(fragmentManager.descriptorYaml.getFragmentUrl().toURI()).toFile().getName(); + if (variant == 2) { + String mainFile = fragmentManager.descriptorYaml.getFragmentPath().toFile().getName(); properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, Parameter.ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, Parameter.ENTITY_NAME), mainFile); properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, YamlPlan.PLANS_ENTITY_NAME), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); diff --git a/step-ap-ide/src/main/resources/work-initial/jmeterProject1/jmeterProject1.xml b/step-ap-ide/src/main/resources/work-initial/jmeterProject1/jmeterProject1.xml new file mode 100644 index 0000000000..3ed06774ee --- /dev/null +++ b/step-ap-ide/src/main/resources/work-initial/jmeterProject1/jmeterProject1.xml @@ -0,0 +1,4 @@ + + + + diff --git a/step-ap-ide/src/main/resources/work-initial/jsProject/jsSample.js b/step-ap-ide/src/main/resources/work-initial/jsProject/jsSample.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/step-ap-ide/src/main/resources/work-initial/lib/fakeLib.jar b/step-ap-ide/src/main/resources/work-initial/lib/fakeLib.jar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/step-ap-ide/src/main/resources/work-initial/nodeProject/nodeSample.ts b/step-ap-ide/src/main/resources/work-initial/nodeProject/nodeSample.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java index 813aad256b..aab8c3d53d 100644 --- a/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java +++ b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackageFunctionCollection.java @@ -29,7 +29,7 @@ public class AutomationPackageFunctionCollection extends InMemoryCollection AutomationPackageParameter.forContext(context, parameter), Parameter.ENTITY_NAME)); + return fragmentManager.saveAdditionalFieldObject(super.save(parameter), AutomationPackageParameter.fromParameter(parameter), Parameter.ENTITY_NAME); } @Override diff --git a/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java index a68b9488a6..610e88cf8a 100644 --- a/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java +++ b/step-automation-packages/step-automation-packages-collections/src/main/java/step/core/collections/AutomationPackagePlanCollection.java @@ -29,7 +29,7 @@ public class AutomationPackagePlanCollection extends InMemoryCollection im private final AutomationPackageYamlFragmentManager fragmentManager; public AutomationPackagePlanCollection(AutomationPackageYamlFragmentManager fragmentManager) { - super(true, YamlPlan.PLANS_ENTITY_NAME); + super(false, YamlPlan.PLANS_ENTITY_NAME); this.fragmentManager = fragmentManager; initialzeRecordsFromFragments(fragmentManager); } @@ -41,7 +41,7 @@ private void initialzeRecordsFromFragments(AutomationPackageYamlFragmentManager @Override public Plan save(Plan p) { - return super.save(fragmentManager.savePlan(p)); + return fragmentManager.savePlan(super.save(p)); } @Override diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java index 3ed25186ed..9d3a1560e6 100644 --- a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageCollectionTestBase.java @@ -84,9 +84,13 @@ protected void assertFilesEqual(Path expected, Path actual) throws IOException { } protected void setPropertiesWriteToFragment(String entityName, String fragment) { + setPropertiesWriteMode(entityName, fragment, AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT); + } + + protected void setPropertiesWriteMode(String entityName, String path, AutomationPackageYamlFragmentManager.NewObjectFragmentMode mode) { Properties properties = new Properties(); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, entityName), fragment); - properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, entityName), AutomationPackageYamlFragmentManager.NewObjectFragmentMode.FRAGMENT.name()); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_PATH, entityName), path); + properties.setProperty(String.format(AutomationPackageYamlFragmentManager.PROPERTY_NEW_OBJECT_FRAGMENT_MODE, entityName), mode.name()); fragmentManager.setProperties(properties); } diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageFragmentReferenceTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageFragmentReferenceTest.java new file mode 100644 index 0000000000..e70d95fa6a --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageFragmentReferenceTest.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (C) 2026, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.collections; + +import org.junit.Before; +import org.junit.Test; +import step.artefacts.Echo; +import step.artefacts.Sequence; +import step.automation.packages.AutomationPackageReadingException; +import step.automation.packages.model.YamlAutomationPackageKeyword; +import step.automation.packages.yaml.AutomationPackageYamlFragmentManager; +import step.core.artefacts.AbstractArtefact; +import step.core.dynamicbeans.DynamicValue; +import step.core.plans.Plan; +import step.functions.Function; +import step.plans.parser.yaml.YamlPlan; +import step.plugins.functions.types.CompositeFunction; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertFalse; + +public class AutomationPackageFragmentReferenceTest extends AutomationPackageCollectionTestBase { + + private Collection functionCollection; + private Collection planCollection; + + public AutomationPackageFragmentReferenceTest() { + super(); + } + + @Before + public void setUp() throws IOException, AutomationPackageReadingException { + super.setUp(); + AutomationPackageCollectionFactory collectionFactory = new AutomationPackageCollectionFactory(new Properties(), fragmentManager); + planCollection = collectionFactory.getCollection(YamlPlan.PLANS_ENTITY_NAME, Plan.class); + functionCollection = collectionFactory.getCollection(YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME, Function.class); + } + + + @Test + public void testAddCompositeKeywordToNewFragmentAndRenameAndRemove() throws IOException { + + Sequence sequence = new Sequence(); + Echo echo = new Echo(); + echo.setText(new DynamicValue<>("Hello World")); + sequence.addChild(echo); + + Plan plan = new Plan(sequence); + + CompositeFunction function = new CompositeFunction(); + function.setPlan(plan); + + Map attributes = new HashMap<>(); + attributes.put(AbstractArtefact.NAME, "Hello World Composite Function"); + function.setAttributes(attributes); + + setPropertiesWriteMode(YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME, "newKeywordsPath", AutomationPackageYamlFragmentManager.NewObjectFragmentMode.PER_OBJECT); + + functionCollection.save(function); + + assertFilesEqual( + expectedFilesPath + .resolve("Hello World Composite Function.yml"), + destinationDirectory.toPath() + .resolve("newKeywordsPath") + .resolve("Hello World Composite Function.yml") + ); + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewKeywordsFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml") + ); + + attributes.put(AbstractArtefact.NAME, "This Keyword was renamed"); + + functionCollection.save(function); + + assertFilesEqual( + expectedFilesPath + .resolve("This Keyword was renamed.yml"), + destinationDirectory.toPath() + .resolve("newKeywordsPath") + .resolve("This Keyword was renamed.yml") + ); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewKeywordsFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + + functionCollection.remove(Filters.equals("attributes.name", "This Keyword was renamed")); + + assertFalse(Files.exists( + destinationDirectory.toPath() + .resolve("newKeywordsPath") + .resolve("This Keyword was renamed.yml") + )); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewKeywordsFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + } + + @Test + public void testAddPlanToNewFragmentAndRenameAndRemove() 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(AbstractArtefact.NAME, "Hello World Plan"); + plan.setAttributes(attributes); + + setPropertiesWriteMode(YamlPlan.PLANS_ENTITY_NAME, "newPlansPath", AutomationPackageYamlFragmentManager.NewObjectFragmentMode.PER_OBJECT); + + planCollection.save(plan); + + assertFilesEqual( + expectedFilesPath + .resolve("Hello World Plan.yml"), + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("Hello World Plan.yml") + ); + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml") + ); + + attributes.put(AbstractArtefact.NAME, "This Plan was renamed"); + + planCollection.save(plan); + + assertFilesEqual( + expectedFilesPath + .resolve("This Plan was renamed.yml"), + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("This Plan was renamed.yml") + ); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + + planCollection.remove(Filters.equals("attributes.name", "This Plan was renamed")); + + assertFalse(Files.exists( + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("This Plan was renamed.yml") + )); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + } + + @Test + public void testAddTwoPlansToNewFragmentAndRemoveOne() 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(AbstractArtefact.NAME, "Hello World Plan"); + plan.setAttributes(attributes); + + setPropertiesWriteMode(YamlPlan.PLANS_ENTITY_NAME, "newPlansPath", AutomationPackageYamlFragmentManager.NewObjectFragmentMode.PER_OBJECT); + + planCollection.save(plan); + + assertFilesEqual( + expectedFilesPath + .resolve("Hello World Plan.yml"), + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("Hello World Plan.yml") + ); + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml") + ); + + Plan plan2 = new Plan(sequence); + Map attributes2 = new HashMap<>(); + attributes2.put(AbstractArtefact.NAME, "This Plan was renamed"); + plan2.setAttributes(attributes2); + + planCollection.save(plan2); + + assertFilesEqual( + expectedFilesPath + .resolve("This Plan was renamed.yml"), + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("This Plan was renamed.yml") + ); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + + planCollection.remove(Filters.equals("attributes.name", "Hello World Plan")); + + assertFalse(Files.exists( + destinationDirectory.toPath() + .resolve("newPlansPath") + .resolve("Hello World Plan.yml") + )); + + assertFilesEqual( + expectedFilesPath + .resolve("descriptorAfterNewPlansFragmentReference.yml"), + destinationDirectory.toPath() + .resolve("automation-package.yml")); + } +} diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java index 5a52ed56b5..e331f0e15a 100644 --- a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageKeywordCollectionTest.java @@ -82,4 +82,18 @@ public void testModifyCompositeKeyword() throws IOException { assertFilesEqual(expectedFilesPath.resolve("keywordsAfterCompositeModification.yml"), destinationDirectory.toPath().resolve("keywords.yml")); } + @Test + public void testRenameCompositeKeyword() throws IOException { + Optional optionalFunction = functionCollection.find(Filters.equals("attributes.name", "Composite keyword from AP"), null, null, null, 100).findFirst(); + assertTrue(optionalFunction.isPresent()); + + CompositeFunction compositeFunction = (CompositeFunction) optionalFunction.get(); + + compositeFunction.getAttributes().put(AbstractOrganizableObject.NAME, "Renamed Composite Keyword"); + + functionCollection.save(compositeFunction); + + assertFilesEqual(expectedFilesPath.resolve("keywordsAfterCompositeRenamed.yml"), destinationDirectory.toPath().resolve("keywords.yml")); + } + } diff --git a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java index 8c33637c55..c08d588e7b 100644 --- a/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java +++ b/step-automation-packages/step-automation-packages-collections/src/test/java/step/core/collections/AutomationPackageParameterCollectionTest.java @@ -57,6 +57,9 @@ public void testParameterModify() throws IOException { Parameter parameter = optionalParameter.get(); parameter.getValue().setValue("myModifiedValue"); + + setPropertiesWriteToFragment(Parameter.ENTITY_NAME, "parameters.yml"); + parameterCollection.save(parameter); assertFilesEqual(expectedFilesPath.resolve("parametersAfterModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); @@ -72,6 +75,7 @@ public void testParameterAddAndModify() throws IOException { setPropertiesWriteToFragment(Parameter.ENTITY_NAME, "parameters.yml"); + parameter.setPriority(1); parameterCollection.save(parameter); assertFilesEqual(expectedFilesPath.resolve("parametersAfterAdd.yml"), destinationDirectory.toPath().resolve("parameters.yml")); @@ -80,6 +84,7 @@ public void testParameterAddAndModify() throws IOException { parameterCollection.save(parameter); parameter.setValue(new DynamicValue<>("foo")); + parameter.setPriority(null); parameterCollection.save(parameter); assertFilesEqual(expectedFilesPath.resolve("parametersAfterAddAndModification.yml"), destinationDirectory.toPath().resolve("parameters.yml")); diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/Hello World Composite Function.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/Hello World Composite Function.yml new file mode 100644 index 0000000000..10a286e1a9 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/Hello World Composite Function.yml @@ -0,0 +1,10 @@ +--- +keywords: + - Composite: + name: "Hello World Composite Function" + plan: + root: + sequence: + children: + - echo: + text: "Hello World" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Keyword was renamed.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Keyword was renamed.yml new file mode 100644 index 0000000000..6216e9e111 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Keyword was renamed.yml @@ -0,0 +1,10 @@ +--- +keywords: + - Composite: + name: "This Keyword was renamed" + plan: + root: + sequence: + children: + - echo: + text: "Hello World" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Plan was renamed.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Plan was renamed.yml new file mode 100644 index 0000000000..55ef8445fc --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/This Plan was renamed.yml @@ -0,0 +1,8 @@ +--- +plans: + - name: "This Plan was renamed" + root: + sequence: + children: + - echo: + text: "Hello World" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewKeywordsFragmentReference.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewKeywordsFragmentReference.yml new file mode 100644 index 0000000000..b213e5a266 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewKeywordsFragmentReference.yml @@ -0,0 +1,26 @@ +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: [] +parameters: + - key: "paramInMainAP" + value: "once" +fragments: + - "keywords.yml" + - "plans/*.yml" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.yml" + - "newKeywordsPath/*.yml" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewPlansFragmentReference.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewPlansFragmentReference.yml new file mode 100644 index 0000000000..7724332157 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/descriptorAfterNewPlansFragmentReference.yml @@ -0,0 +1,26 @@ +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: [] +parameters: + - key: "paramInMainAP" + value: "once" +fragments: + - "keywords.yml" + - "plans/*.yml" + - "schedules.yml" + - "parameters.yml" + - "parameters2.yml" + - "unknown.yml" + - "newPlansPath/*.yml" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/keywordsAfterCompositeRenamed.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/keywordsAfterCompositeRenamed.yml new file mode 100644 index 0000000000..e4ad5103a8 --- /dev/null +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/keywordsAfterCompositeRenamed.yml @@ -0,0 +1,29 @@ +keywords: + - JMeter: + name: "JMeter keyword from automation package" + description: "JMeter keyword 1" + executeLocally: false + useCustomTemplate: true + callTimeout: 1000 + jmeterTestplan: "jmeterProject1/jmeterProject1.xml" + - Composite: + name: "Renamed Composite Keyword" + 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" diff --git a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml index 0ab61a81ad..3997b99ca0 100644 --- a/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml +++ b/step-automation-packages/step-automation-packages-collections/src/test/resources/expected/parametersAfterAdd.yml @@ -15,3 +15,4 @@ parameters: - key: "addedParameter" value: "test" description: "This is an added Parameter before modification" + priority: 1 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 4fead6c631..531b779cac 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 @@ -29,6 +29,7 @@ import step.automation.packages.yaml.model.AutomationPackageFragmentYaml; import step.core.plans.Plan; import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchableYamlPrimitive; import step.functions.Function; import step.plans.automation.YamlPlainTextPlan; import step.plans.nl.RootArtefactType; @@ -42,13 +43,11 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; import java.util.stream.Collectors; /** @@ -130,7 +129,7 @@ protected AutomationPackageContent buildAutomationPackage(AutomationPackageDescr // apply imported fragments recursively if (descriptor != null) { - fillAutomationPackageWithImportedFragments(res, descriptor, archive, new HashMap<>()); + fillAutomationPackageWithImportedFragments(res, descriptor, archive, new HashSet<>()); } return res; } @@ -183,33 +182,54 @@ protected AutomationPackageContent newContentInstance() { public AutomationPackageYamlFragmentManager getAutomationPackageYamlFragmentManager(T archive) throws AutomationPackageReadingException { AutomationPackageDescriptorReader reader = getOrCreateDescriptorReader(); - URL descriptorURL = archive.getDescriptorYamlUrl(); - try (InputStream inputStream = descriptorURL.openStream()) { + URL descriptorUrl = archive.getDescriptorYamlUrl(); + try (InputStream inputStream = descriptorUrl.openStream()) { AutomationPackageDescriptorYaml descriptor = reader.readAutomationPackageDescriptor(inputStream, archive.getOriginalFileName()); - descriptor.setFragmentUrl(descriptorURL); - AutomationPackageContent content = newContentInstance(); - Map fragmentMap = new ConcurrentHashMap<>(); - fillAutomationPackageWithImportedFragments(content, descriptor, archive, fragmentMap); - StagingAutomationPackageContext stagingContext = new StagingAutomationPackageContext(null, AutomationPackageOperationMode.LOCAL, new LocalResourceManagerImpl(Path.of(descriptorURL.getPath()).getParent().toFile()), archive, content, null, null, new HashMap<>()); - return new AutomationPackageYamlFragmentManager(descriptor, fragmentMap, getOrCreateDescriptorReader(), stagingContext); + + try { + Path descriptorPath = Path.of(descriptorUrl.toURI()); + descriptor.setFragmentPath(descriptorPath); + AutomationPackageContent content = newContentInstance(); + Set fragments = new HashSet<>(); + fillAutomationPackageWithImportedFragments(content, descriptor, archive, fragments); + StagingAutomationPackageContext stagingContext = new StagingAutomationPackageContext(null, AutomationPackageOperationMode.LOCAL, new LocalResourceManagerImpl(descriptorPath.getParent().toFile()), archive, content, null, null, new HashMap<>()); + return new AutomationPackageYamlFragmentManager(archive.getResourcePathMatchingResolver(), descriptor, fragments, getOrCreateDescriptorReader(), stagingContext); + } catch (FileSystemNotFoundException | URISyntaxException e) { + throw new AutomationPackageReadingException("Failed to read automation package for editing. The most likely cause is that you were trying to load " + + "an automation package as a packaged jar. This is not supported and expected behaviour", e); + } } 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 { + /** + * + * @param targetPackage Target Automation package content to be filled by fragment read entities + * @param fragment Fragment to read + * @param archive Automation package archive + * @param fragments Set of all automation package fragments collected during recursive reading of fragments. + * @throws AutomationPackageReadingException Thrown upon errors when reading the fragment + */ + private void fillAutomationPackageWithImportedFragments(AutomationPackageContent targetPackage, AutomationPackageFragmentYaml fragment, T archive, Set fragments) throws AutomationPackageReadingException { fillContentSections(targetPackage, fragment, archive); if (!fragment.getFragments().isEmpty()) { - for (String importedFragmentReference : fragment.getFragments()) { - List resources = archive.getResourcesByPattern(importedFragmentReference); + for (PatchableYamlPrimitive importedFragmentReference : fragment.getFragments()) { + List resources = archive.getResourcesByPattern(importedFragmentReference.toString()); for (URL resource : resources) { try (InputStream fragmentYamlStream = resource.openStream()) { - fragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, resource.toString(), archive.getAutomationPackageName()); - fragmentYamlMap.put(resource.toString(), fragment); - fragment.setFragmentUrl(resource); - fillAutomationPackageWithImportedFragments(targetPackage, fragment, archive, fragmentYamlMap); - } catch (IOException e) { + AutomationPackageFragmentYaml referencedFragment = getOrCreateDescriptorReader().readAutomationPackageFragment(fragmentYamlStream, resource.toString(), archive.getAutomationPackageName()); + fragments.add(referencedFragment); + try { + referencedFragment.setFragmentPath(Path.of(resource.toURI())); + } catch (FileSystemNotFoundException e) { + logger.warn("Could not set Fragment path for fragment editing while loading fragment. " + + "This is likely due to loading the automation package as a jar and not as a file system folder. This is expected behaviour"); + } + + fillAutomationPackageWithImportedFragments(targetPackage, referencedFragment, archive, fragments); + } catch (IOException | URISyntaxException e) { throw new AutomationPackageReadingException("Unable to read fragment in automation package: " + importedFragmentReference, e); } } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageDescriptorReader.java index 4e139641cc..3a9aa56898 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 @@ -167,6 +167,7 @@ private ObjectMapper createYamlObjectMapper() { // Disable native type id to enable conversion to generic Documents yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); yamlFactory.enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR); + yamlFactory.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); ObjectMapper yamlMapper = DefaultJacksonMapperProvider.getObjectMapper(yamlFactory); diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java index a6487f001b..eeea9e7f57 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/AutomationPackageYamlFragmentManager.java @@ -18,6 +18,8 @@ ******************************************************************************/ package step.automation.packages.yaml; +import org.apache.commons.io.FileUtils; +import step.automation.packages.ResourcePathMatchingResolver; import step.automation.packages.StagingAutomationPackageContext; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.automation.packages.yaml.model.AutomationPackageDescriptorYaml; @@ -25,12 +27,14 @@ import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; import step.core.accessors.AbstractOrganizableObject; import step.core.plans.Plan; +import step.core.yaml.NamedObjectPatchableYamlModel; import step.core.yaml.PatchableYamlModel; import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.PatchingContext; +import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; import step.core.yaml.deserialization.AutomationPackagePerObjectSaveUnsupportedException; -import step.core.yaml.deserialization.AutomationPackageUpdateException; import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchableYamlPrimitive; import step.functions.Function; import step.parameter.Parameter; import step.parameter.automation.AutomationPackageParameter; @@ -39,24 +43,25 @@ import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.file.Path; -import java.text.MessageFormat; import java.util.Map; import java.util.Optional; import java.util.Properties; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import java.util.stream.Stream; public class AutomationPackageYamlFragmentManager { protected final Path apRoot; protected final StagingAutomationPackageContext stagingContext; + private final ResourcePathMatchingResolver resourcePatchMatchingResolver; public enum NewObjectFragmentMode { /** @@ -75,24 +80,24 @@ public enum NewObjectFragmentMode { protected final Map patchableMap = new ConcurrentHashMap<>(); protected final Map fragmentMap = new ConcurrentHashMap<>(); - protected final Map pathToYamlFragment; + protected final Set fragments; protected Properties properties = new Properties(); public final AutomationPackageFragmentYaml descriptorYaml; - public AutomationPackageYamlFragmentManager(AutomationPackageDescriptorYaml descriptorYaml, Map fragmentMap, AutomationPackageDescriptorReader descriptorReader, StagingAutomationPackageContext stagingContext) { - + public AutomationPackageYamlFragmentManager(ResourcePathMatchingResolver resourcePathMatchingResolver, AutomationPackageDescriptorYaml descriptorYaml, Set fragments, AutomationPackageDescriptorReader descriptorReader, StagingAutomationPackageContext stagingContext) { + this.resourcePatchMatchingResolver = resourcePathMatchingResolver; this.descriptorReader = descriptorReader; this.descriptorYaml = descriptorYaml; - pathToYamlFragment = fragmentMap; - apRoot = Path.of(descriptorYaml.getFragmentUrl().getPath()) - .getParent(); + apRoot = descriptorYaml.getFragmentPath().getParent(); this.stagingContext = stagingContext; initializeMaps(descriptorYaml); - pathToYamlFragment.values().stream() + this.fragments = fragments; + + fragments.stream() .filter(f -> f != descriptorYaml) .forEach(this::initializeMaps); } @@ -102,7 +107,6 @@ public void setProperties(Properties properties) { } public void initializeMaps(AutomationPackageFragmentYaml fragment) { - pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); for (YamlPlan yamlPlan : fragment.getPlans()) { Plan plan = descriptorReader.getPlanReader().yamlPlanToPlan(yamlPlan); patchableMap.put(plan, yamlPlan); @@ -145,13 +149,12 @@ public synchronized Plan savePlan(Plan plan) { AutomationPackageFragmentYaml fragment = fragmentMap.get(plan); if (fragment == null) { - fragment = fragmentForNewObject(plan, YamlPlan.PLANS_ENTITY_NAME); + fragment = fragmentForNewObject(newYamlPlan, YamlPlan.PLANS_ENTITY_NAME); fragmentMap.put(plan, fragment); - pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); addFragmentEntity(fragment, fragment.getPlans(), newYamlPlan); } else { YamlPlan oldYamlPlan = (YamlPlan) patchableMap.get(plan); - modifyFragmentEntity(fragment, fragment.getPlans(), oldYamlPlan, newYamlPlan); + modifyFragmentEntity(fragment, fragment.getPlans(), oldYamlPlan, newYamlPlan, YamlPlan.PLANS_ENTITY_NAME); } patchableMap.put(plan, newYamlPlan); @@ -161,11 +164,10 @@ public synchronized Plan savePlan(Plan plan) { public synchronized step.functions.Function saveFunction(step.functions.Function function) { AutomationPackageFragmentYaml fragment = fragmentMap.get(function); + YamlAutomationPackageKeyword newKeyword = createNewYamlKeyword(function); if (fragment == null) { - fragment = fragmentForNewObject(function, "keywords"); + fragment = fragmentForNewObject(newKeyword, YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME); fragmentMap.put(function, fragment); - pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - YamlAutomationPackageKeyword newKeyword = createNewYamlKeyword(function); if (newKeyword != null) { patchableMap.put(function, newKeyword); addFragmentEntity(fragment, fragment.getKeywords(), newKeyword); @@ -173,9 +175,9 @@ public synchronized step.functions.Function saveFunction(step.functions.Function System.err.println("SAVING OF FUNCTION OF TYPE " + function.getClass().getName() + " IS NOT CURRENTLY SUPPORTED"); } } else { - YamlAutomationPackageKeyword yamlKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); - yamlKeyword.getYamlKeyword().updateFromFunction(function); - modifyFragmentEntity(fragment, fragment.getKeywords(), yamlKeyword, yamlKeyword); + YamlAutomationPackageKeyword oldKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); + modifyFragmentEntity(fragment, fragment.getKeywords(), oldKeyword, newKeyword, YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME); + patchableMap.put(function, newKeyword); } return function; } @@ -222,54 +224,136 @@ private void addFragmentEntity(AutomationPackageF fragment.writeToDisk(); } - private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T oldEntity, T newEntity) { + private void modifyFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, T oldEntity, T newEntity, String fieldName) { entityList.replaceItem(oldEntity, newEntity); + Path oldRelativePath = determineObjectRelativePath(oldEntity, fieldName, false); + Path newRelativePath = determineObjectRelativePath(newEntity, fieldName, false); + + // Path did not change - skip entire move logic + if (!oldRelativePath.equals(newRelativePath)) { + Path oldAbsolutePath = apRoot.resolve(oldRelativePath); + + /* oldRelativePath is the path which would have been given to old version of the entity + by the fragment manager. If it matches the fragment path, this means that + the fragment path was intended to follow the naming convention based on the configuration + (i.e. PER_OBJECT naming) + + if the paths don't match, then simply skip the renaming. This silently allows for: + - legacy fragments which don't follow the naming convention + - FRAGMENT type naming (fixed fragment for object types such as Parameters) + */ + if (oldAbsolutePath.equals(fragment.getFragmentPath())) { + Path newAbsolutePath = apRoot.resolve(newRelativePath); + + try { + FileUtils.moveFile(oldAbsolutePath.toFile(), newAbsolutePath.toFile()); + fragment.setFragmentPath(newAbsolutePath); + } catch (IOException e) { + throw new AutomationPackageConcurrentEditException( + String.format("Unable to rename file %s to file %s. Was the file renamed or deleted outside the editor?", oldAbsolutePath, newAbsolutePath)); + } + + AutomationPackageFragmentYaml referencingFragment = determineReferencingFragment(oldRelativePath) + .orElse(descriptorYaml); + + String newReference = resourcePatchMatchingResolver.getFragmentReferenceString(determineObjectRelativePath(newEntity, fieldName, true)); + String oldReference = resourcePatchMatchingResolver.getFragmentReferenceString(oldRelativePath); + + if (referencingFragment.getFragments().removeIf(f -> f.getValue().equals(oldReference))) { + referencingFragment.getFragments().add(new PatchableYamlPrimitive<>(referencingFragment.getPatchingContext(), newReference)); + referencingFragment.writeToDisk(); + }; + } + } fragment.writeToDisk(); } - private AutomationPackageFragmentYaml fragmentForNewObject(AbstractOrganizableObject p, String fieldName) { + private void removeFragmentEntity(AutomationPackageFragmentYaml fragment, PatchableYamlList entityList, BO object) { - NewObjectFragmentMode mode = NewObjectFragmentMode.valueOf(properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.PER_OBJECT.name())); - String defaultRelativeFragmentPath = fieldName; - if (mode == NewObjectFragmentMode.FRAGMENT) { - defaultRelativeFragmentPath = defaultRelativeFragmentPath + ".yml"; + PatchableYamlModel yamlObject = patchableMap.get(object); + entityList.remove(yamlObject); + + patchableMap.remove(object); + fragmentMap.remove(object); + + if (fragment.isEmpty()) { + try { + FileUtils.delete(fragment.getFragmentPath().toFile()); + + determineReferencingFragment(fragment.getFragmentPath()).ifPresent(referencingFragment -> { + String relativeFragmentReference = resourcePatchMatchingResolver + .getFragmentReferenceString(apRoot.relativize(fragment.getFragmentPath())); + if (referencingFragment.getFragments().removeIf(f -> f.getValue().equals(relativeFragmentReference))) { + referencingFragment.writeToDisk(); + }; + fragments.remove(fragment); + }); + } catch (IOException e) { + throw new AutomationPackageConcurrentEditException(String.format("%s was removed outside the editor", fragment.getFragmentPath())); + } + } else { + fragment.writeToDisk(); } + } + private AutomationPackageFragmentYaml fragmentForNewObject(PatchableYamlModel p, String fieldName) { - if (mode == NewObjectFragmentMode.PER_OBJECT && !p.hasAttribute(AbstractOrganizableObject.NAME)) { - throw new AutomationPackagePerObjectSaveUnsupportedException(String.format(""" - Saving by object name is unsupported for %1$s, please configure the entity to be stored in a specified single fragment, i.e. + Path path = determineObjectRelativePath(p, fieldName, false); + Path absolutePath = apRoot.resolve(path); - %2$s = %1$s.yml - %3$s = %4$s - """, fieldName, String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.FRAGMENT.name())); + Optional optionalExistingFragment = fragmentMap + .values().stream().filter(f -> f.getFragmentPath().equals(absolutePath)).findAny(); + if (optionalExistingFragment.isPresent()) { + return optionalExistingFragment.get(); } - String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); - Path path = new File(relativeFragmentPath).toPath(); - if (!path.isAbsolute()) { - path = apRoot.resolve(path); - } + PatchingContext context = new PatchingContext(absolutePath.toString(), "---", descriptorYaml.getPatchingContext().getMapper()); + AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); + fragments.add(fragment); + fragment.setFragmentPath(absolutePath); - if (mode == NewObjectFragmentMode.PER_OBJECT) { - path = path.resolve(sanitizeFilename(p.getAttribute(AbstractOrganizableObject.NAME)) + ".yml"); + if (determineReferencingFragment(path).isEmpty()) { + String referencingPath = resourcePatchMatchingResolver.getFragmentReferenceString(determineObjectRelativePath(p, fieldName, true)); + descriptorYaml.getFragments().add(new PatchableYamlPrimitive<>(descriptorYaml.getPatchingContext(), referencingPath)); + descriptorYaml.writeToDisk(); } + return fragment; + } - try { - URL url = path.toUri().toURL(); + private Optional determineReferencingFragment(Path path) { + return Stream.concat(Stream.of(descriptorYaml), fragments.stream()) + .filter(fragment -> fragment.getFragments().stream() + .anyMatch(pattern -> resourcePatchMatchingResolver.isMatchingPath(pattern.getValue(), path))) + .findFirst(); + } + public Path determineObjectRelativePath(PatchableYamlModel p, String fieldName, boolean globPattern) { - if (pathToYamlFragment.containsKey(url.toString())) { - return pathToYamlFragment.get(url.toString()); - } - PatchingContext context = new PatchingContext(url.toString(), "---", descriptorYaml.getPatchingContext().getMapper()); - AutomationPackageFragmentYaml fragment = new AutomationPackageFragmentYamlImpl(context); - fragment.setFragmentUrl(url); - return fragment; - } catch (MalformedURLException e) { - throw new AutomationPackageUpdateException(MessageFormat.format("Error creating path for new fragment: {0}", path), e); + NewObjectFragmentMode mode = NewObjectFragmentMode.valueOf(properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.PER_OBJECT.name())); + String defaultRelativeFragmentPath = fieldName; + + if (mode == NewObjectFragmentMode.FRAGMENT) { + defaultRelativeFragmentPath = defaultRelativeFragmentPath + ".yml"; } + String relativeFragmentPath = properties.getProperty(String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), defaultRelativeFragmentPath); + + return switch (mode) { + case NewObjectFragmentMode.FRAGMENT -> new File(relativeFragmentPath).toPath(); + case NewObjectFragmentMode.PER_OBJECT -> { + if (p instanceof NamedObjectPatchableYamlModel namedObjectPatchableYamlModel) { + String name = globPattern ? "*" : namedObjectPatchableYamlModel.getName(); + yield new File(relativeFragmentPath).toPath().resolve(sanitizeFilename(name + ".yml")); + } + + throw new AutomationPackagePerObjectSaveUnsupportedException(String.format(""" + Saving by object name is unsupported for %1$s, please configure the entity to be stored in a specified single fragment, i.e. + + %2$s = %1$s.yml + %3$s = %4$s + """, fieldName, String.format(PROPERTY_NEW_OBJECT_FRAGMENT_PATH, fieldName), String.format(PROPERTY_NEW_OBJECT_FRAGMENT_MODE, fieldName), NewObjectFragmentMode.FRAGMENT.name())); + } + }; } public String sanitizeFilename(String inputName) { @@ -278,64 +362,35 @@ public String sanitizeFilename(String inputName) { public void removePlan(Plan plan) { AutomationPackageFragmentYaml fragment = fragmentMap.get(plan); - YamlPlan yamlPlan = (YamlPlan) patchableMap.get(plan); - - fragment.getPlans().remove(yamlPlan); - - patchableMap.remove(plan); - fragmentMap.remove(plan); - - fragment.writeToDisk(); + removeFragmentEntity(fragment, fragment.getPlans(), plan); } public void removeFunction(step.functions.Function function) { AutomationPackageFragmentYaml fragment = fragmentMap.get(function); - YamlAutomationPackageKeyword yamlKeyword = (YamlAutomationPackageKeyword) patchableMap.get(function); - - fragment.getKeywords().remove(yamlKeyword); - - patchableMap.remove(function); - fragmentMap.remove(function); - - fragment.writeToDisk(); + removeFragmentEntity(fragment, fragment.getKeywords(), function); } public void removeAdditionalFieldObject(BO object, String fieldName) { - AutomationPackageFragmentYaml fragment = fragmentMap.get(object); - PatchableYamlModel yamlObject = patchableMap.get(object); + removeFragmentEntity(fragment, fragment.getAdditionalField(fieldName), object); + } - fragment.getAdditionalField(fieldName) - .remove(yamlObject); + public synchronized BO saveAdditionalFieldObject(BO object, YO yamlObject, String fieldName) { + final AutomationPackageFragmentYaml fragment = fragmentMap.computeIfAbsent(object, o -> fragmentForNewObject(yamlObject, fieldName)); - patchableMap.remove(object); - fragmentMap.remove(object); + yamlObject.setPatchingContext(fragment.getPatchingContext()); + PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields() + .computeIfAbsent(fieldName, f -> new PatchableYamlList(fragment.getPatchingContext(), fieldName)); - fragment.writeToDisk(); - } - public synchronized BO saveAdditionalFieldObject(BO object, java.util.function.Function newYamlObjectCreator, String fieldName) { - AutomationPackageFragmentYaml fragment; - if (fragmentMap.get(object) == null) { - fragment = fragmentForNewObject(object, fieldName); - - YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); - PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields() - .computeIfAbsent(fieldName, k -> new PatchableYamlList(fragment.getPatchingContext(), fieldName)); - fragmentMap.put(object, fragment); - pathToYamlFragment.put(fragment.getFragmentUrl().toString(), fragment); - addFragmentEntity(fragment, list, newYamlObject); - patchableMap.put(object, newYamlObject); + if (patchableMap.get(object) == null) { + addFragmentEntity(fragment, list, yamlObject); + patchableMap.put(object, yamlObject); } else { - fragment = fragmentMap.get(object); - YO newYamlObject = newYamlObjectCreator.apply(fragment.getPatchingContext()); - PatchableYamlList list = (PatchableYamlList) fragment.getAdditionalFields() - .computeIfAbsent(fieldName, k -> new PatchableYamlList(fragment.getPatchingContext(), fieldName)); - YO oldYamlObject = (YO) patchableMap.get(object); - modifyFragmentEntity(fragment, list, oldYamlObject, newYamlObject); - patchableMap.put(object, newYamlObject); + modifyFragmentEntity(fragment, list, oldYamlObject, yamlObject, fieldName); + patchableMap.put(object, yamlObject); } return object; } diff --git a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java index 815cfaaa2f..4a92941594 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java +++ b/step-automation-packages/step-automation-packages-yaml/src/main/java/step/automation/packages/yaml/deserialization/AbstractYamlAutomationPackageFragmentDeserializer.java @@ -27,25 +27,19 @@ import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import step.automation.packages.deserialization.AutomationPackageSerializationRegistry; import step.automation.packages.yaml.model.AbstractAutomationPackageFragmentYaml; -import step.automation.packages.yaml.model.AutomationPackageFragmentYamlImpl; -import step.core.yaml.PatchingContext; import step.core.yaml.deserialization.PatchableYamlList; import java.io.IOException; public abstract class AbstractYamlAutomationPackageFragmentDeserializer extends BeanDeserializer implements ContextualDeserializer, ResolvableDeserializer { - private final BeanDeserializer delegate; public AbstractYamlAutomationPackageFragmentDeserializer(BeanDeserializer deserializer) { super(deserializer); - delegate = deserializer; } @Override - public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return deserialize(p, ctxt, new AutomationPackageFragmentYamlImpl((PatchingContext) ctxt.getAttribute(PatchingContext.class))); - } + abstract public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException; @Override protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt, Object intoValue, String propName) throws IOException { 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 c4891c8311..f715264730 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,34 +18,29 @@ ******************************************************************************/ package step.automation.packages.yaml.model; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.annotation.*; import org.apache.commons.io.FileUtils; import step.automation.packages.model.YamlAutomationPackageKeyword; import step.automation.packages.yaml.AutomationPackageWriteToDiskException; import step.core.yaml.PatchingContext; import step.core.yaml.deserialization.AutomationPackageConcurrentEditException; import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchableYamlPrimitive; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @JsonInclude(JsonInclude.Include.NON_EMPTY) public abstract class AbstractAutomationPackageFragmentYaml implements AutomationPackageFragmentYaml { - private List fragments = new ArrayList<>(); + private PatchableYamlList> fragments; private PatchableYamlList keywords; private PatchableYamlList plans; private PatchableYamlList plansPlainText; @@ -59,10 +54,11 @@ public AbstractAutomationPackageFragmentYaml(PatchingContext patchingContext) { plans = new PatchableYamlList<>(patchingContext, YamlPlan.PLANS_ENTITY_NAME); keywords = new PatchableYamlList<>(patchingContext, YamlAutomationPackageKeyword.KEYWORDS_ENTITY_NAME); plansPlainText = new PatchableYamlList<>(patchingContext, "plansPlainText"); + fragments = new PatchableYamlList<>(patchingContext, "fragments"); } @JsonIgnore - private URL url; + private Path path; @Override public PatchableYamlList getKeywords() { @@ -85,12 +81,12 @@ public void setPlans(PatchableYamlList plans) { } @Override - public List getFragments() { + public PatchableYamlList> getFragments() { return fragments; } @JsonSetter(nulls = Nulls.AS_EMPTY) - public void setFragments(List fragments) { + public void setFragments(PatchableYamlList> fragments) { this.fragments = fragments; } @@ -116,9 +112,9 @@ public void setPlansPlainText(PatchableYamlList plansPlainTex } @JsonIgnore - public void setFragmentUrl(URL url) { + public void setFragmentPath(Path path) { resetLastModified(); - this.url = url; + this.path = path; } private void resetLastModified() { @@ -126,8 +122,8 @@ private void resetLastModified() { } @JsonIgnore - public URL getFragmentUrl() { - return url; + public Path getFragmentPath() { + return path; } @JsonIgnore @@ -146,14 +142,23 @@ public PatchingContext getPatchingContext() { @Override public void writeToDisk() { try { - File file = new File(url.toURI()); + File file = path.toFile(); if (file.exists() && file.lastModified() > fileLastModified) { - throw new AutomationPackageConcurrentEditException(MessageFormat.format("Automation package fragment {0} was edited outside the editor.", url)); + throw new AutomationPackageConcurrentEditException(MessageFormat.format("Automation package fragment {0} was edited outside the editor.", path)); } FileUtils.writeStringToFile(file, context.getCurrentYaml(), StandardCharsets.UTF_8); resetLastModified(); - } catch (IOException | URISyntaxException e) { - throw new AutomationPackageWriteToDiskException(MessageFormat.format("Error when writing automation package fragment {0} back to disk.", url), e); + } catch (IOException e) { + throw new AutomationPackageWriteToDiskException(MessageFormat.format("Error when writing automation package fragment {0} back to disk.", path), e); } } + + @Override + public boolean isEmpty() { + return getFragments().isEmpty() && + getPlans().isEmpty() && + getPlansPlainText().isEmpty() && + getKeywords().isEmpty() && + getAdditionalFields().isEmpty(); + } } 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 7cf2be19b3..8721c6963f 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 @@ -21,11 +21,12 @@ import step.automation.packages.model.YamlAutomationPackageKeyword; import step.core.yaml.PatchingContext; import step.core.yaml.deserialization.PatchableYamlList; +import step.core.yaml.deserialization.PatchableYamlPrimitive; import step.plans.automation.YamlPlainTextPlan; import step.plans.parser.yaml.YamlPlan; import java.io.IOException; -import java.net.URL; +import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -37,7 +38,7 @@ public interface AutomationPackageFragmentYaml { List getPlansPlainText(); - List getFragments(); + PatchableYamlList> getFragments(); Map> getAdditionalFields(); @@ -47,13 +48,15 @@ default PatchableYamlList getAdditionalField(String k) { void setAdditionalFields(String key, PatchableYamlList value) throws IOException; - URL getFragmentUrl(); + Path getFragmentPath(); - void setFragmentUrl(URL url); + void setFragmentPath(Path url); PatchingContext getPatchingContext(); void setPatchingContext(PatchingContext context); void writeToDisk(); + + boolean isEmpty(); } diff --git a/step-automation-packages/step-automation-packages-yaml/src/test/java/step/automation/packages/yaml/AutomationPackageDescriptorReaderTest.java b/step-automation-packages/step-automation-packages-yaml/src/test/java/step/automation/packages/yaml/AutomationPackageDescriptorReaderTest.java index a751c0a7a5..c03d302bd2 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/test/java/step/automation/packages/yaml/AutomationPackageDescriptorReaderTest.java +++ b/step-automation-packages/step-automation-packages-yaml/src/test/java/step/automation/packages/yaml/AutomationPackageDescriptorReaderTest.java @@ -34,6 +34,7 @@ import java.io.InputStream; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import static org.junit.Assert.*; @@ -100,7 +101,7 @@ public void completeDescriptorReadTest() throws AutomationPackageReadingExceptio assertEquals("*/5 * * * *", firstTask.getCron()); assertEquals("TEST", firstTask.getExecutionParameters().get("environment")); - assertEquals(Arrays.asList("importPlans.yml", "importKeywords.yml"), descriptor.getFragments()); + assertEquals(Arrays.asList("importPlans.yml", "importKeywords.yml"), descriptor.getFragments().stream().map(f -> f.getValue()).collect(Collectors.toUnmodifiableList())); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/step-automation-packages/step-automation-packages-yaml/src/test/java/step/core/yaml/PatchingContextTest.java b/step-automation-packages/step-automation-packages-yaml/src/test/java/step/core/yaml/PatchingContextTest.java index d4279eddfb..60987b1ba4 100644 --- a/step-automation-packages/step-automation-packages-yaml/src/test/java/step/core/yaml/PatchingContextTest.java +++ b/step-automation-packages/step-automation-packages-yaml/src/test/java/step/core/yaml/PatchingContextTest.java @@ -103,7 +103,6 @@ public class PatchingContextTest { schemaVersion: 1.0.0 name: "complete-package" fragments: - - "importPlans.yml" - "importKeywords.yml" keywords: - Composite: @@ -151,7 +150,6 @@ public class PatchingContextTest { schemaVersion: 1.0.0 name: "complete-package" fragments: - - "importPlans.yml" - "importKeywords.yml" keywords: [] plans: [] @@ -186,35 +184,35 @@ public void testLowLevelDeletionModification() throws Exception { .filter(m -> m instanceof PatchableYamlList) .map(x -> (PatchableYamlList) x) .toList(); - assertEquals(3, lists.size()); + assertEquals(4, lists.size()); // remove first keyword, second plan, second schedule lists.get(0).remove(0); - lists.get(1).remove(1); + lists.get(1).remove(0); lists.get(2).remove(1); + lists.get(3).remove(1); current = ensureValid(patchingContext.getCurrentYaml()); Assert.assertEquals(EXPECTED_REMOVED_1, current); // empty first two lists, modify schedule a little - AutomationPackageSchedule schedule1 = (AutomationPackageSchedule) lists.get(2).getFirst(); + AutomationPackageSchedule schedule1 = (AutomationPackageSchedule) lists.get(3).getFirst(); schedule1.setName("This is now the new schedule name which is rather long, really long in fact"); schedule1.setActive(false); schedule1.getExecutionParameters().clear(); // we have to tell the entity explicitly that it was modified schedule1.setModified(); - lists.subList(0, 2).forEach(ArrayList::removeFirst); + lists.subList(1, 3).forEach(ArrayList::removeFirst); current = ensureValid(patchingContext.getCurrentYaml()); Assert.assertEquals(EXPECTED_REMOVED_2_MODIFIED, current); - lists.get(2).removeFirst(); + lists.get(3).removeFirst(); current = ensureValid(patchingContext.getCurrentYaml()); Assert.assertEquals(""" schemaVersion: 1.0.0 name: "complete-package" fragments: - - "importPlans.yml" - "importKeywords.yml" keywords: [] plans: [] diff --git a/step-core-model/src/main/java/step/core/yaml/NamedObjectPatchableYamlModel.java b/step-core-model/src/main/java/step/core/yaml/NamedObjectPatchableYamlModel.java new file mode 100644 index 0000000000..3c2e741e38 --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/NamedObjectPatchableYamlModel.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (C) 2026, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.yaml; + +public interface NamedObjectPatchableYamlModel { + + String getName(); +} diff --git a/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java index ce6f3bbd53..d11289b8ec 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchableYamlModelBase.java @@ -27,7 +27,7 @@ public class PatchableYamlModelBase extends AbstractYamlModel implements Patchab private static final Logger logger = LoggerFactory.getLogger(PatchableYamlModelBase.class); @JsonIgnore - private PatchingContext context; + protected PatchingContext context; public PatchableYamlModelBase(PatchingContext context) { this.context = context; diff --git a/step-core-model/src/main/java/step/core/yaml/PatchingContext.java b/step-core-model/src/main/java/step/core/yaml/PatchingContext.java index b5df67456b..02c8f5af3f 100644 --- a/step-core-model/src/main/java/step/core/yaml/PatchingContext.java +++ b/step-core-model/src/main/java/step/core/yaml/PatchingContext.java @@ -7,11 +7,8 @@ import org.slf4j.LoggerFactory; import step.core.yaml.deserialization.AutomationPackageUpdateException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.nio.file.Path; +import java.util.*; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -32,13 +29,13 @@ public PatchingContext() { } public PatchingContext(ObjectMapper mapper) { - this(null, "", mapper); + this("", "", mapper); } public PatchingContext(String sourceLocation, String yaml, ObjectMapper mapper) { - this.sourceLocation = sourceLocation; - this.initialLines = new CopyOnWriteArrayList<>(yaml.lines().toList()); - this.mapper = mapper; + this.sourceLocation = Objects.requireNonNull(sourceLocation); + this.initialLines = new CopyOnWriteArrayList<>(Objects.requireNonNull(yaml).lines().toList()); + this.mapper = Objects.requireNonNull(mapper); } public ObjectMapper getMapper() { @@ -66,21 +63,19 @@ private List getLines(ChunkBounds bounds) { return initialLines.subList(bounds.startLineNumber - 1, bounds.endLineNumber); } - public record ChunkBounds(int startLineNumber, int endLineNumber) implements Comparable - - { + public record ChunkBounds(int startLineNumber, int endLineNumber) implements Comparable { private static final Comparator COMPARATOR = Comparator .comparingInt(ChunkBounds::startLineNumber) // lower startLine first .thenComparing(Comparator.comparingInt(ChunkBounds::endLineNumber).reversed()); // larger endLine (i.e. larger chunk) first @Override - public int compareTo (ChunkBounds that){ - return COMPARATOR.compare(this, that); - } + public int compareTo(ChunkBounds that) { + return COMPARATOR.compare(this, that); + } - public boolean encompasses (ChunkBounds inner){ - return inner.startLineNumber >= this.startLineNumber && inner.endLineNumber <= this.endLineNumber; - } + public boolean encompasses(ChunkBounds inner) { + return inner.startLineNumber >= this.startLineNumber && inner.endLineNumber <= this.endLineNumber; + } } public String getCurrentYaml() { @@ -151,9 +146,7 @@ private List getClaimedOuterBounds() { private String serializeUnindented(Object entity) { try { - return mapper.writeValueAsString(entity) - .replaceFirst("^---\\s*\\n", "") - .trim(); + return mapper.writeValueAsString(entity); } catch (JsonProcessingException e) { throw new AutomationPackageUpdateException("Error Serializing YAML object", e); } diff --git a/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlPrimitive.java b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlPrimitive.java new file mode 100644 index 0000000000..8b2c60719c --- /dev/null +++ b/step-core-model/src/main/java/step/core/yaml/deserialization/PatchableYamlPrimitive.java @@ -0,0 +1,57 @@ +package step.core.yaml.deserialization; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.JsonLocation; +import step.core.yaml.PatchableYamlModelBase; +import step.core.yaml.PatchingContext; + +import java.util.Objects; + +/******************************************************************************* + * Copyright (C) 2026, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +public class PatchableYamlPrimitive extends PatchableYamlModelBase { + @JsonIgnore + private T value; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public PatchableYamlPrimitive(@JacksonInject(useInput = OptBoolean.FALSE) PatchingContext context, T value) { + super(context); + this.value = value; + Objects.requireNonNull(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @JsonValue + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + @Override + public void onParsed(JsonLocation startLocation, JsonLocation endLocation) { + context.claimChunk(startLocation, startLocation, this); + } +} diff --git a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java index aecc711f00..40616b4dca 100644 --- a/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java +++ b/step-core-model/src/main/java/step/parameter/automation/AutomationPackageParameter.java @@ -103,8 +103,8 @@ public String getScopeEntity() { return scopeEntity; } - public static AutomationPackageParameter forContext(PatchingContext context, Parameter parameter) { - AutomationPackageParameter yamlParameter = new AutomationPackageParameter(context); + public static AutomationPackageParameter fromParameter(Parameter parameter) { + AutomationPackageParameter yamlParameter = new AutomationPackageParameter(null); yamlParameter.copyFieldsFromObject(parameter, true); Expression expression = parameter.getActivationExpression(); if (expression == null) { diff --git a/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java b/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java index 19553ff896..79bc36a272 100644 --- a/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java +++ b/step-core/src/main/java/step/automation/packages/ResourcePathMatchingResolver.java @@ -18,10 +18,15 @@ ******************************************************************************/ package step.automation.packages; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -88,7 +93,7 @@ private void findPathMatchingResourcesRecursive(String[] pathArray, int currentL if (nextLevel < pathArray.length) { String nextPath = pathArray[nextLevel]; List urls = ClassLoaderResourceFilesystem.listDirectory(currentPath); - Pattern pattern = Pattern.compile(nextPath.replaceAll("\\*", ".*")); + Pattern pattern = prepareMatchPattern(nextPath); for (URL url : urls) { String file = url.getFile(); if (file.endsWith(getPathSeparator())) { @@ -109,4 +114,15 @@ private void findPathMatchingResourcesRecursive(String[] pathArray, int currentL } } + private Pattern prepareMatchPattern(String referencingString) { + return Pattern.compile(referencingString.replace("*", ".*")); + } + + public boolean isMatchingPath(String referenceString, Path path) { + return prepareMatchPattern(referenceString).matcher(getFragmentReferenceString(path)).matches(); + } + + public String getFragmentReferenceString(Path path) { + return path.toString().replace(File.pathSeparator, getPathSeparator()); + } } diff --git a/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java b/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java index 451275e59e..97462ba1a6 100644 --- a/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java +++ b/step-core/src/main/java/step/automation/packages/model/AbstractYamlFunction.java @@ -20,11 +20,11 @@ import jakarta.json.JsonObject; import step.automation.packages.StagingAutomationPackageContext; -import step.core.yaml.YamlModelUtils; import step.core.accessors.AbstractOrganizableObject; import step.core.dynamicbeans.DynamicValue; import step.core.yaml.AbstractYamlModel; import step.core.yaml.YamlFieldCustomCopy; +import step.core.yaml.YamlModelUtils; import step.functions.Function; import step.jsonschema.JsonSchema; import step.jsonschema.JsonSchemaDefaultValueProvider; @@ -120,11 +120,6 @@ public T applyAutomationPackageContext(StagingAutomationPackageContext context) return res; } - public void updateFromFunction(Function function) { - copyFieldsFromObject(function, false); - } - - public static class DefaultYamlFunctionNameProvider implements JsonSchemaDefaultValueProvider { public DefaultYamlFunctionNameProvider() { diff --git a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java index e1e76a63c9..0c1af166d0 100644 --- a/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java +++ b/step-core/src/main/java/step/automation/packages/model/YamlAutomationPackageKeyword.java @@ -19,11 +19,13 @@ package step.automation.packages.model; import step.automation.packages.StagingAutomationPackageContext; +import step.core.yaml.NamedObjectPatchableYamlModel; import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.PatchingContext; import step.functions.Function; -public class YamlAutomationPackageKeyword extends PatchableYamlModelBase implements AutomationPackageKeyword { +public class YamlAutomationPackageKeyword extends PatchableYamlModelBase + implements AutomationPackageKeyword, NamedObjectPatchableYamlModel { private AbstractYamlFunction yamlKeyword; @@ -34,6 +36,7 @@ public YamlAutomationPackageKeyword(AbstractYamlFunction yamlKeyword, Patchin } + public AbstractYamlFunction getYamlKeyword() { return yamlKeyword; } @@ -46,4 +49,9 @@ public void setYamlKeyword(AbstractYamlFunction yamlKeyword) { public Function prepareKeyword(StagingAutomationPackageContext context) { return yamlKeyword.applyAutomationPackageContext(context); } + + @Override + public String getName() { + return yamlKeyword.getName(); + } } 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 74ba1898bb..28429275ea 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plans/parser/yaml/YamlPlan.java @@ -27,6 +27,7 @@ import step.core.plans.agents.configuration.AgentProvisioningConfiguration; import step.core.plans.agents.configuration.AgentProvisioningConfigurationDeserializer; import step.core.plans.agents.configuration.AgentProvisioningConfigurationSerializer; +import step.core.yaml.NamedObjectPatchableYamlModel; import step.core.yaml.PatchableYamlModelBase; import step.core.yaml.PatchingContext; import step.core.yaml.model.NamedYamlArtefact; @@ -34,7 +35,7 @@ import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) -public class YamlPlan extends PatchableYamlModelBase { +public class YamlPlan extends PatchableYamlModelBase implements NamedObjectPatchableYamlModel { public static final String PLANS_ENTITY_NAME = "plans"; @@ -53,6 +54,7 @@ public YamlPlan(@JacksonInject(useInput = OptBoolean.FALSE, optional = OptBoolea super(context); } + @Override public String getName() { return name; } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/functions/types/automation/YamlCompositeFunction.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/functions/types/automation/YamlCompositeFunction.java index b58d97518d..f4ef80c2cc 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/functions/types/automation/YamlCompositeFunction.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/functions/types/automation/YamlCompositeFunction.java @@ -19,17 +19,13 @@ package step.plugins.functions.types.automation; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; import step.automation.packages.StagingAutomationPackageContext; import step.automation.packages.model.AbstractYamlFunction; import step.core.accessors.AbstractOrganizableObject; import step.core.plans.Plan; import step.core.yaml.YamlFieldCustomCopy; import step.core.yaml.YamlModel; -import step.core.yaml.model.AbstractYamlArtefact; -import step.core.yaml.model.NamedYamlArtefact; import step.core.yaml.schema.YamlJsonSchemaHelper; -import step.functions.Function; import step.jsonschema.JsonSchema; import step.plans.parser.yaml.YamlPlan; import step.plugins.functions.types.CompositeFunction; @@ -65,26 +61,6 @@ protected void fillDeclaredFields(CompositeFunction res, StagingAutomationPackag } } - @Override - public void updateFromFunction(Function function) { - copyFieldsFromObject(function, false); - - if (function instanceof CompositeFunction) { - Plan plan = ((CompositeFunction) function).getPlan(); - // plan name is optional, the composite function name is used by default - // FIXME: discuss what exactly this is supposed to do and how it relates to the comment above :-) - if (this.plan.getName() != null && !this.plan.getName().isEmpty()) { - this.plan.setName(plan.getAttribute(AbstractOrganizableObject.NAME)); - } - // I have no idea why that mapper would be null sometimes: - // ObjectMapper mapper = this.plan.getRoot().getYamlArtefact().getYamlObjectMapper(); - // That one should work... - ObjectMapper mapper = this.getPlan().getPatchingContext().getMapper(); - this.plan.setRoot(new NamedYamlArtefact(AbstractYamlArtefact.toYamlArtefact(plan.getRoot(), mapper))); - } - } - - private Plan yamlPlanToPlan(YamlPlan yamlPlan) { Plan plan = new Plan(yamlPlan.getRoot().getYamlArtefact().toArtefact());