This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
The following commit(s) were added to refs/heads/master by this push: new 5b1ea500dc expression parser - tweak to multi-word handling 5b1ea500dc is described below commit 5b1ea500dcdbe71d2c12490906a680e251f4f722 Author: Alex Heneveld <a...@cloudsoft.io> AuthorDate: Thu Feb 8 15:32:03 2024 +0000 expression parser - tweak to multi-word handling simplify non-final-match-raw semantics --- .../brooklyn/core/workflow/ShorthandProcessor.java | 3 +- .../core/workflow/ShorthandProcessorEpToQst.java | 22 ++-- .../brooklyn/core/workflow/WorkflowBasicTest.java | 76 +++++++++--- .../core/workflow/WorkflowMapAndListTest.java | 15 +-- .../core/workflow/WorkflowOperandsTest.java | 14 +-- .../workflow/WorkflowParsingEdgeCasesTest.java | 134 +++++++++++++++++++++ 6 files changed, 209 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessor.java b/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessor.java index 0adbaffc23..356d655705 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessor.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessor.java @@ -37,7 +37,8 @@ public class ShorthandProcessor { return delegate.process(input); } - /** whether the last match should preserve quotes and spaces; default false */ + /** optionally skip the automatic unwrapping of a single quoted last word lining up with the last word of a template; + * by default, we unwrap in that one special case, to facilitate eg return "something fancy" */ public ShorthandProcessor withFinalMatchRaw(boolean finalMatchRaw) { delegate.withFinalMatchRaw(finalMatchRaw); return this; diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessorEpToQst.java b/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessorEpToQst.java index cb94600636..6e48f8dc80 100644 --- a/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessorEpToQst.java +++ b/core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessorEpToQst.java @@ -83,7 +83,8 @@ public class ShorthandProcessorEpToQst { return new ShorthandProcessorQstAttempt(this, input).call(); } - /** whether the last match should preserve quotes and spaces; default false */ + /** optionally skip the automatic unwrapping of a single quoted last word lining up with the last word of a template; + * by default, we unwrap in that one special case, to facilitate eg return "something fancy" */ public ShorthandProcessorEpToQst withFinalMatchRaw(boolean finalMatchRaw) { this.finalMatchRaw = finalMatchRaw; return this; @@ -131,7 +132,6 @@ public class ShorthandProcessorEpToQst { inputRemaining = Strings.trimStart(inputRemaining); if (Strings.isNonBlank(inputRemaining)) { if (valueUpdater!=null) { - //QuotedStringTokenizer qstInput = qst(inputRemaining); valueUpdater.accept(getRemainderPossiblyRaw(inputRemaining)); } else { // shouldn't come here @@ -340,19 +340,13 @@ public class ShorthandProcessorEpToQst { return mp.get(); } private Maybe<String> getRemainderPossiblyRawEp(String inputRemaining) { - if (options.finalMatchRaw) { - return Maybe.of(inputRemaining); - } - Maybe<List<ParseNodeOrValue>> mp = ShorthandProcessorExprParser.tokenizer().parseEverything(inputRemaining); - return mp.map(pnl -> { - final boolean UNQUOTE_INDIVIDUAL_WORDS = false; // legacy behaviour - - if (pnl.size()==1 || UNQUOTE_INDIVIDUAL_WORDS) { - return ExpressionParser.getAllUnquoted(pnl); - } else { - return ExpressionParser.getUnescapedButNotUnquoted(pnl); + if (!options.finalMatchRaw) { + Maybe<List<ParseNodeOrValue>> mp = ShorthandProcessorExprParser.tokenizer().parseEverything(inputRemaining); + if (mp.isPresent() && mp.get().size()==1 && ExpressionParser.isQuotedExpressionNode(mp.get().get(0))) { + return Maybe.of(ExpressionParser.getUnquoted( mp.get().get(0) )); } - }); + } + return Maybe.of(inputRemaining); } private String getNextInputTokenUpToPossibleExpectedLiteralQst(QuotedStringTokenizer qstInput, String nextLiteral) { diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java index 364a7090e9..1ab20b9887 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowBasicTest.java @@ -18,6 +18,12 @@ */ package org.apache.brooklyn.core.workflow; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.reflect.TypeToken; import org.apache.brooklyn.api.entity.Entity; @@ -45,13 +51,38 @@ import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypes; -import org.apache.brooklyn.core.workflow.steps.*; -import org.apache.brooklyn.core.workflow.steps.appmodel.*; +import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.AddEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.AddPolicyWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ApplyInitializerWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ClearConfigWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ClearSensorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeleteEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeletePolicyWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeployApplicationWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.InvokeEffectorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ReparentEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetConfigWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetEntityNameWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetSensorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.UpdateChildrenWorkflowStep; import org.apache.brooklyn.core.workflow.steps.external.HttpWorkflowStep; import org.apache.brooklyn.core.workflow.steps.external.ShellWorkflowStep; import org.apache.brooklyn.core.workflow.steps.external.SshWorkflowStep; -import org.apache.brooklyn.core.workflow.steps.flow.*; -import org.apache.brooklyn.core.workflow.steps.variables.*; +import org.apache.brooklyn.core.workflow.steps.flow.FailWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.ForeachWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.GotoWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.NoOpWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.RetryWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.ReturnWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.SleepWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.SwitchWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.ClearVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.LoadWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.SetVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.WaitWorkflowStep; import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.test.Asserts; @@ -63,14 +94,9 @@ import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; +import org.apache.commons.lang3.tuple.Pair; import org.testng.annotations.Test; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport { @@ -172,6 +198,23 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport { } } + static Pair<BasicApplication,Object> runStepsInNewApp(ManagementContext mgmt, List<?> steps) { + WorkflowBasicTest.addWorkflowStepTypes(mgmt); + BasicApplication app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); + Task<?> invocation = runSteps(app, steps); + return Pair.of(app, invocation.getUnchecked()); + } + + static Task<?> runSteps(BasicApplication app, List<?> steps) { + WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance() + .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow") + .configure(WorkflowEffector.STEPS, (List) steps) + ); + eff.apply((EntityLocal) app); + Task<?> invocation = app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null); + return invocation; + } + @Test public void testStepResolution() throws JsonProcessingException { loadTypes(); @@ -376,16 +419,9 @@ public class WorkflowBasicTest extends BrooklynMgmtUnitTestSupport { loadTypes(); BasicApplication app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); - WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance() - .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow") - .configure(WorkflowEffector.STEPS, MutableList.of( - WorkflowTestStep.of( setup::accept ), - "set-sensor " + (type!=null ? type+" " : "") + "x = " + expression - )) - ); - eff.apply((EntityLocal)app); - - Task<?> invocation = app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null); + Task<?> invocation = runSteps(app, MutableList.of( + WorkflowTestStep.of(setup::accept), + "set-sensor " + (type != null ? type + " " : "") + "x = " + expression)); invocation.getUnchecked(); Dumper.dumpInfo(invocation); diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowMapAndListTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowMapAndListTest.java index c42bd46223..738545dfcc 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowMapAndListTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowMapAndListTest.java @@ -25,6 +25,7 @@ import java.util.Map; import com.google.common.collect.ImmutableMap; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.Dumper; import org.apache.brooklyn.core.entity.EntityAsserts; @@ -36,6 +37,7 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; @@ -47,16 +49,9 @@ public class WorkflowMapAndListTest extends BrooklynMgmtUnitTestSupport { private BasicApplication app; Object runSteps(List<?> steps) { - WorkflowBasicTest.addWorkflowStepTypes(mgmt); - - BasicApplication app = mgmt().getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); - this.app = app; - WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance() - .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow") - .configure(WorkflowEffector.STEPS, (List) steps) - ); - eff.apply((EntityLocal)app); - return app.invoke(app.getEntityType().getEffectorByName ("myWorkflow").get(), null).getUnchecked(); + Pair<BasicApplication, Object> result = WorkflowBasicTest.runStepsInNewApp(mgmt(), steps); + this.app = result.getLeft(); + return result.getRight(); } @Test diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowOperandsTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowOperandsTest.java index 4f61339c9e..435348928b 100644 --- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowOperandsTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowOperandsTest.java @@ -25,6 +25,7 @@ import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.commons.lang3.tuple.Pair; import org.testng.annotations.Test; import java.util.List; @@ -34,16 +35,9 @@ public class WorkflowOperandsTest extends BrooklynMgmtUnitTestSupport { private BasicApplication app; Object runSteps(List<?> steps) { - WorkflowBasicTest.addWorkflowStepTypes(mgmt); - - BasicApplication app = mgmt().getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); - this.app = app; - WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance() - .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow") - .configure(WorkflowEffector.STEPS, (List) steps) - ); - eff.apply((EntityLocal)app); - return app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null).getUnchecked(); + Pair<BasicApplication, Object> result = WorkflowBasicTest.runStepsInNewApp(mgmt(), steps); + this.app = result.getLeft(); + return result.getRight(); } public Object evaluate(String expression, String type) { diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java new file mode 100644 index 0000000000..1b2a646eda --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowParsingEdgeCasesTest.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.workflow; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.reflect.TypeToken; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.Dumper; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.resolve.jackson.BeanWithTypePlanTransformer; +import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; +import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; +import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.AddEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.AddPolicyWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ApplyInitializerWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ClearConfigWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ClearSensorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeleteEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeletePolicyWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.DeployApplicationWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.InvokeEffectorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.ReparentEntityWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetConfigWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetEntityNameWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.SetSensorWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.appmodel.UpdateChildrenWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.external.HttpWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.external.ShellWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.external.SshWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.FailWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.ForeachWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.GotoWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.NoOpWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.RetryWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.ReturnWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.SleepWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.flow.SwitchWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.ClearVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.LoadWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.SetVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep; +import org.apache.brooklyn.core.workflow.steps.variables.WaitWorkflowStep; +import org.apache.brooklyn.core.workflow.store.WorkflowStatePersistenceViaSensors; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.ClassLogWatcher; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.commons.lang3.tuple.Pair; +import org.testng.annotations.Test; + + +public class WorkflowParsingEdgeCasesTest extends BrooklynMgmtUnitTestSupport { + + private BasicApplication app; + + Object runSteps(String ...steps) { + Pair<BasicApplication, Object> result = WorkflowBasicTest.runStepsInNewApp(mgmt(), Arrays.asList(steps)); + this.app = result.getLeft(); + return result.getRight(); + } + + @Test + public void testBackslashEscaping() { + final String BS = "\\"; + + String UNQUOTED_INPUT = "double unquoted " + BS + BS; + String QUOTED_INPUT_UNQUOTED = "quoted " + BS + BS; + String QUOTED_INPUT = "\"" + QUOTED_INPUT_UNQUOTED + "\""; + String QUOTED_INPUT_UNQUOTED_UNESCAPED = "quoted " + BS; + + // entire phrase quoted will be unquoted and unescaped + Asserts.assertEquals(runSteps("return " + QUOTED_INPUT), QUOTED_INPUT_UNQUOTED_UNESCAPED); + // but unquoted won't be + Asserts.assertEquals(runSteps("return " + UNQUOTED_INPUT), UNQUOTED_INPUT); + // and partial words won't be even if quoted + Asserts.assertEquals(runSteps("return " + UNQUOTED_INPUT + " and " + QUOTED_INPUT), + UNQUOTED_INPUT + " and " + QUOTED_INPUT); + + // however 'let' does do unquoting on word level (according to EP words) + Asserts.assertEquals(runSteps("let x = "+UNQUOTED_INPUT+" and "+QUOTED_INPUT, "return ${x}"), + UNQUOTED_INPUT + " and " + QUOTED_INPUT_UNQUOTED_UNESCAPED); + } + +}