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 cc86e2f4dd transform syntax improved
cc86e2f4dd is described below

commit cc86e2f4dde74c5ae52ca6373243a0723903896c
Author: Alex Heneveld <[email protected]>
AuthorDate: Tue Jun 20 13:32:19 2023 +0100

    transform syntax improved
    
    add explicit "variable" or "value"; and only the former will update in 
place.
    disallow (for now) `transform ambiguous_thing | some_transform` -- 
requiring `ambiguous_thing` to be an expression
---
 .../brooklyn/core/workflow/ShorthandProcessor.java |   8 ++
 .../workflow/WorkflowExpressionResolution.java     |  39 ++++--
 .../core/workflow/WorkflowStepDefinition.java      |  12 +-
 .../steps/appmodel/UpdateChildrenWorkflowStep.java |   4 +-
 .../steps/variables/SetVariableWorkflowStep.java   |   4 +-
 .../variables/TransformVariableWorkflowStep.java   | 142 ++++++++++++++++-----
 .../core/workflow/ShorthandProcessorTest.java      |   2 +
 .../brooklyn/core/workflow/WorkflowBasicTest.java  |  29 +++++
 .../core/workflow/WorkflowMapAndListTest.java      |   2 -
 .../brooklyn/core/workflow/WorkflowSizeTest.java   |   9 +-
 .../core/workflow/WorkflowTransformTest.java       |  79 ++++++++++--
 11 files changed, 261 insertions(+), 69 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 0e81dfc966..32ba2f835c 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
@@ -46,6 +46,14 @@ import java.util.stream.Collectors;
  * "LITERAL" - to expect a literal expression. this must include the quotation 
marks and should include spaces if spaces are required.
  * [ TOKEN ] - to indicate TOKEN is optional, where TOKEN is one of the above 
sections. parsing is attempted first with it, then without it.
  * [ ?${VAR} TOKEN ] - as `[ TOKEN ]` but VAR is set true or false depending 
whether this optional section was matched.
+ *
+ * Would be nice to support A | B (exclusive or) for A or B but not both 
(where A might contain a literal for disambiguation),
+ * and ( X ) for X required but grouped (for use with | (exclusive or) where 
one option is required).
+ * Would also be nice to support any order, which could be ( A & B ) to allow 
A B or B A.
+ *
+ * But for now we've made do without it, with some compromises:
+ * * keywords must follow the order indicated
+ * * exclusive alternatives are disallowed by code subsequently or checked 
separately (eg Transform)
  */
 public class ShorthandProcessor {
 
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExpressionResolution.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExpressionResolution.java
index a18b78d8bd..76bc842329 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExpressionResolution.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExpressionResolution.java
@@ -389,25 +389,42 @@ public class WorkflowExpressionResolution {
     /** does not use templates */
     public <T> T resolveCoercingOnly(Object expression, TypeToken<T> type) {
         if (expression==null) return null;
+        boolean triedCoercion = false;
+        List<Exception> exceptions = MutableList.of();
+        if (expression instanceof String) {
+            try {
+                // prefer simple coercion if it's a string coming in
+                return TypeCoercions.coerce(expression, type);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                exceptions.add(e);
+                triedCoercion = true;
+            }
+        }
+
         if (Jsonya.isJsonPrimitiveDeep(expression) && !(expression instanceof 
Set)) {
             try {
-                // only try yaml coercion, as values are normally set from 
yaml and will be raw at this stage (but not if they are from a DSL)
-                // (might be better to always to TC.coerce)
+                // next try yaml coercion for anything complex, as values are 
normally set from yaml and will be raw at this stage (but not if they are from 
a DSL)
                 return 
BeanWithTypeUtils.convert(context.getManagementContext(), expression, type, 
true,
                         
RegisteredTypes.getClassLoadingContext(context.getEntity()), true /* needed for 
wrapped resolved holders */);
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
-                try {
-                    // fallback to simple coercion
-                    return TypeCoercions.coerce(expression, type);
-                } catch (Exception e2) {
-                    Exceptions.propagateIfFatal(e2);
-                    throw Exceptions.propagate(e);
-                }
+                exceptions.add(e);
             }
-        } else {
-            return TypeCoercions.coerce(expression, type);
         }
+
+        if (!triedCoercion) {
+            try {
+                // fallback to simple coercion
+                return TypeCoercions.coerce(expression, type);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                exceptions.add(e);
+                triedCoercion = true;
+            }
+        }
+
+        throw Exceptions.propagate(exceptions.iterator().next());
     }
 
     static class WorkflowVariableResolutionStackEntry {
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepDefinition.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepDefinition.java
index f6a2cdc54a..f51d962208 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepDefinition.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepDefinition.java
@@ -159,16 +159,18 @@ public abstract class WorkflowStepDefinition {
     abstract public void populateFromShorthand(String value);
 
     protected void populateFromShorthandTemplate(String template, String 
value) {
-        populateFromShorthandTemplate(template, value, false, true);
+        populateFromShorthandTemplate(template, value, false, true, true);
     }
 
-    protected Maybe<Map<String, Object>> populateFromShorthandTemplate(String 
template, String value, boolean finalMatchRaw, boolean failOnMismatch) {
+    protected Map<String, Object> populateFromShorthandTemplate(String 
template, String value, boolean finalMatchRaw, boolean failOnMismatch, boolean 
failOnError) {
         Maybe<Map<String, Object>> result = new 
ShorthandProcessor(template).withFinalMatchRaw(finalMatchRaw).withFailOnMismatch(failOnMismatch).process(value);
-        if (result.isAbsent())
-            throw new IllegalArgumentException("Invalid shorthand expression: 
'" + value + "'", Maybe.Absent.getException(result));
+        if (result.isAbsent()) {
+            if (failOnError) throw new IllegalArgumentException("Invalid 
shorthand expression: '" + value + "'", Maybe.Absent.getException(result));
+            return null;
+        }
 
         input.putAll((Map<? extends String, ?>) 
CollectionMerger.builder().build().merge(input, result.get()));
-        return result;
+        return result.get();
     }
 
     final Task<?> newTask(WorkflowStepInstanceExecutionContext context) {
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/appmodel/UpdateChildrenWorkflowStep.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/appmodel/UpdateChildrenWorkflowStep.java
index a62ef63f66..777b0da50e 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/appmodel/UpdateChildrenWorkflowStep.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/appmodel/UpdateChildrenWorkflowStep.java
@@ -274,8 +274,8 @@ public class UpdateChildrenWorkflowStep extends 
WorkflowStepDefinition implement
         List matches = 
runOrResumeSubWorkflowForPhaseOrReturnPreviousIfCompleted(context, 
instructionsForResuming, subworkflowTargetForResuming,
                 "Matching items against children", stepState.matchCheck, 
MATCH_CHECK_WORKFLOW,
                 () -> new CustomWorkflowStep(MutableList.of(
-                        "transform identifier_expression | resolve_expression 
| set id",
-                        MutableMap.of("step", "fail message 
identifier_expression should be a non-static expression including an 
interpolated reference to item",
+                        "transform ${identifier_expression} | 
resolve_expression | set id",
+                        MutableMap.of("step", "fail message 
identifier_expression should be a non-static expression including an 
interpolated reference to item, instead got ${identifier_expression}",
                             "condition", MutableMap.of("target", 
"${identifier_expression}", "equals", "${id}")),
                         "let child_or_id = ${parent.children[id]} ?? ${id}",
                         "transform child_tostring = ${child_or_id} | 
to_string",
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java
index fb2d2ebfa1..9afebbd3df 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/SetVariableWorkflowStep.java
@@ -73,8 +73,8 @@ public class SetVariableWorkflowStep extends 
WorkflowStepDefinition {
 
     @Override
     public void populateFromShorthand(String expression) {
-        Maybe<Map<String, Object>> newInput = 
populateFromShorthandTemplate(SHORTHAND, expression, true, true);
-        if (newInput.isPresentAndNonNull() && 
newInput.get().get(VALUE.getName())!=null && 
input.get(INTERPOLATION_MODE.getName())==null) {
+        Map<String, Object> newInput = 
populateFromShorthandTemplate(SHORTHAND, expression, true, true, true);
+        if (newInput.get(VALUE.getName())!=null && 
input.get(INTERPOLATION_MODE.getName())==null) {
             setInput(INTERPOLATION_MODE, InterpolationMode.WORDS);
         }
     }
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java
index e014bb661c..86bbb0f58a 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformVariableWorkflowStep.java
@@ -31,8 +31,10 @@ import 
org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
 import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution;
 import org.apache.brooklyn.core.workflow.WorkflowStepDefinition;
 import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext;
-import org.apache.brooklyn.util.collections.*;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.QuotedStringTokenizer;
 import org.apache.brooklyn.util.text.Strings;
@@ -41,7 +43,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -54,19 +59,38 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
 
     private static final Logger log = 
LoggerFactory.getLogger(TransformVariableWorkflowStep.class);
 
-    public static final String SHORTHAND =
-            "[ [ [ ${variable.type} ] [ ?${value_is_initial} \"value\" ] 
${variable.name} " +
-            "[ [ \"=\" ${value...} ] \"|\" ${transform...} ] ] ]";
+    public static final String[] SHORTHAND_OPTIONS = {
+                "[ ${variable.type} ] " +
+                "\"variable\" ${variable.name} " +
+//                "[ \"=\" ${value...} ] " +   // no point in allowing this
+                "[ \"|\" ${transform...} ]",
+
+                "[ ${variable.type} ] " +
+                "\"value\" ${value} " +
+                "[ \"|\" ${transform...} ]",
+
+                "[ ${variable.type} ] " +
+                "${vv_auto} " +
+                "[ \"=\" ${value...} ] " +
+                "[ \"|\" ${transform...} ]",
+    };
 
     public static final ConfigKey<TypedValueToSet> VARIABLE = 
ConfigKeys.newConfigKey(TypedValueToSet.class, "variable");
-    public static final ConfigKey<Boolean> VALUE_IS_INITIAL = 
ConfigKeys.newConfigKey(Boolean.class, "value_is_initial");
     public static final ConfigKey<Object> VALUE = 
ConfigKeys.newConfigKey(Object.class, "value");
+    public static final ConfigKey<Object> VV_AUTO = 
ConfigKeys.newConfigKey(Object.class, "vv_auto");
+
     public static final ConfigKey<Object> TRANSFORM = 
ConfigKeys.newConfigKey(Object.class, "transform");
 
 
     @Override
     public void populateFromShorthand(String expression) {
-        populateFromShorthandTemplate(SHORTHAND, expression, true, true);
+        Map<String, Object> match = null;
+        for (int i=0; i<SHORTHAND_OPTIONS.length && match==null; i++)
+            match = populateFromShorthandTemplate(SHORTHAND_OPTIONS[i], 
expression, false, true, false);
+
+        if (match==null && Strings.isNonBlank(expression)) {
+            throw new IllegalArgumentException("Invalid shorthand expression 
for transform: '" + expression + "'");
+        }
     }
 
     @Override
@@ -80,7 +104,6 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
     @Override
     protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) {
         TypedValueToSet variable = context.getInput(VARIABLE);
-        String name;
 
         Object transformO = context.getInputRaw(TRANSFORM.getName());
         if (!(transformO instanceof Iterable)) transformO = 
MutableList.of(transformO);
@@ -90,36 +113,83 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
             else throw new IllegalArgumentException("Argument to transform 
should be a string or list of strings, not: "+t);
         }
 
-        Object v;
-        if (input.containsKey(VALUE.getName())) {
-            name = variable==null ? null : 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_INPUT,
 variable.name, String.class);
-
-            if (!Strings.isBlank(name) && 
Boolean.TRUE.equals(context.getInput(VALUE_IS_INITIAL))) {
-                // allowed with no var name, we use the value as if "value" 
was specified, or with a var name, the var will be set
-                // but not allowed if "value" keyword was supplied because 
that means the var name is to be treated as the value
-                throw new IllegalArgumentException("Cannot specifiy 
value_is_initial (keyword \"value\") with an = value");
+        String varName = null;
+        Object vv_auto = input.get(VV_AUTO.getName());
+        boolean variableNameSpecified = variable != null && 
Strings.isNonBlank(variable.name);
+
+        Object valueExpressionToTransform = null;
+
+        boolean setVariableAtEnd = variableNameSpecified;
+        Boolean typeCoercionAtStart = null;
+
+        if (Strings.isNonBlank((String)vv_auto)) {
+            boolean isVariable = input.containsKey(VALUE.getName());
+            boolean isValue = variableNameSpecified;
+
+            if (!isVariable && !isValue) {
+                isValue = true;
+                // in future, just do the line above
+                // ...
+                // but for now we need to exclude it if it matches a workflow 
variable because that could be legacy syntax
+                boolean isDisallowedUndecoratedVariable = false;
+                try {
+                    Object vvAutoResolved = 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_INPUT,
 "${" + vv_auto + "}", Object.class);
+                    isDisallowedUndecoratedVariable = true;
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    // otherwise normal, it does not match a variable name, so 
treat it as the value
+                }
+                if (isDisallowedUndecoratedVariable)
+                    throw new IllegalArgumentException("Legacy transform 
syntax `transform var_name | ...` disallowed (" + vv_auto + "); use 
'${"+vv_auto+"'}' if you don't want to update it or insert the keyword 
'variable' if you do");
             }
 
-            v = input.get(VALUE.getName());
-            if (variable!=null && Strings.isNonBlank(variable.type)) 
transforms.add("type "+variable.type);
+            if (isVariable) {
+                if (isValue) throw new IllegalArgumentException("Legacy 
transform identified as both value and variable");
 
-        } else {
-            if (variable==null || Strings.isBlank(variable.name)) throw new 
IllegalArgumentException("Variable name is required");
+                varName = (String) vv_auto;
+                setVariableAtEnd = true;
+                typeCoercionAtStart = false;
 
-            v = "${" + variable.name + "}";
-            if (Boolean.TRUE.equals(context.getInput(VALUE_IS_INITIAL)) || 
"value".equals(variable.type)) {
-                // special keyword to treat name as a literal expression
-                v = variable.name;
-            }
-            if (Strings.isNonBlank(variable.type)) {
-                if (Boolean.TRUE.equals(context.getInput(VALUE_IS_INITIAL)) || 
!"value".equals(variable.type)) {
-                    transforms.add(0, "type " + variable.type);
-                }
+            } else {
+                if (!isValue) throw new IllegalArgumentException("Legacy 
transform cannot identify as value or variable");
+
+                valueExpressionToTransform = vv_auto;
+                typeCoercionAtStart = !variableNameSpecified;
             }
-            name = null;
+        }
+
+        if (variableNameSpecified) {
+            if (varName!=null) throw new IllegalStateException("Variable name 
specified twice"); //shouldn't happen
+            varName = variable.name;
+            setVariableAtEnd = true;
+        }
+
+        if (valueExpressionToTransform==null) {
+            // value not set from auto; check if specified elsewhere
+            valueExpressionToTransform = input.get(VALUE.getName());
+
+            if (typeCoercionAtStart==null && valueExpressionToTransform!=null) 
typeCoercionAtStart = !variableNameSpecified;
+        }
+        if (valueExpressionToTransform==null) {
+            if (!variableNameSpecified) throw new 
IllegalArgumentException("Transform needs a variable or value to transform");
+
+            valueExpressionToTransform = "${"+variable.name+"}";
+            if (typeCoercionAtStart!=null) throw new 
IllegalStateException("Unclear whether to do type coercion; should be unset"); 
// shouldn't come here
+            typeCoercionAtStart = true;
+        }
+
+        if (typeCoercionAtStart==null) {
+            throw new IllegalStateException("Unclear whether to do type 
coercion; should be set"); // shouldn't come here
+        }
+
+        if (variable!=null && Strings.isNonBlank(variable.type)) {
+            // if type is specified with a variable used to start the 
transform, we coerce to that type at the start and at the end
+            if (typeCoercionAtStart) transforms.add(0, "type " + 
variable.type);
+            if (setVariableAtEnd) transforms.add("type " + variable.type);
         }
 
         boolean isResolved = false;
+        Object v = valueExpressionToTransform;
         for (String t: transforms) {
             WorkflowTransformWithContext tt = getTransform(context, t);
 
@@ -129,7 +199,7 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
                 v = 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_RUNNING,
 v, TypeToken.of(Object.class));
                 isResolved = true;
             } else if (!req && isResolved) {
-                throw new IllegalArgumentException("Transform '" + t + "' must 
be first as it requires unresolved input");
+                throw new IllegalArgumentException("Transform '" + t + "' must 
be first or follow a resolve_expression transform as it requires unresolved 
input");
             } else {
                 // requirement matches resolution status, both done, or both 
not
             }
@@ -145,9 +215,13 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
             isResolved = true;
         }
 
-        if (name!=null) {
-            Object oldValue = setWorkflowScratchVariableDotSeparated(context, 
name, v);
-            // these are easily inferred from workflow vars
+        if (setVariableAtEnd) {
+            if (varName==null) throw new IllegalStateException("Variable name 
not specified when setting variable"); // shouldn't happen
+
+            setWorkflowScratchVariableDotSeparated(context, varName, v);
+            // easily inferred from workflow vars, now that updates are stored 
separately
+//            Object oldValue =
+//              <above> setWorkflowScratchVariableDotSeparated(context, 
varName, v);
 //            context.noteOtherMetadata("Value set", v);
 //            if (oldValue != null) context.noteOtherMetadata("Previous 
value", oldValue);
 
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/workflow/ShorthandProcessorTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/workflow/ShorthandProcessorTest.java
index 3b32b00fc8..bb52a32ade 100644
--- 
a/core/src/test/java/org/apache/brooklyn/core/workflow/ShorthandProcessorTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/core/workflow/ShorthandProcessorTest.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.core.workflow;
 
 import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
+import 
org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.testng.annotations.Test;
@@ -160,4 +161,5 @@ the logic will take the one which matches the optional 
word1 but as minimally as
         assertShorthandOfGives("[ [ ${a} ] ${b} [ \"=\" ${c...} ] ]", "b = c", 
MutableMap.of("b", "b", "c", "c"));
         assertShorthandOfGives("[ [ ${a} ] ${b} [ \"=\" ${c...} ] ]", "a b = 
c", MutableMap.of("a", "a", "b", "b", "c", "c"));
     }
+
 }
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 16e88d8d99..e8da1bf0de 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
@@ -450,4 +450,33 @@ public class WorkflowBasicTest extends 
BrooklynMgmtUnitTestSupport {
                     ));
         }
     }
+
+    @Test
+    public void testConditionResolvesAndExactlyOnce() {
+        loadTypes();
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+        WorkflowExecutionContext w1 = WorkflowBasicTest.runWorkflow(app, 
Strings.lines(
+                "steps:",
+                " - step: let a = b",
+                " - step: let b = c",
+                " - step: let list result = []",
+                " - step: transform variable result | append a=b",
+                "   condition:",
+                "     target: ${a}",
+                "     equals: b",
+                " - step: transform variable result | append a=c",
+                "   condition:",
+                "     target: ${a}",
+                "     equals: c",
+                " - step: transform variable result | append b=c",
+                "   condition:",
+                "     target: ${b}",
+                "     equals: c",
+                " - return ${result}"
+        ), null);
+        Asserts.assertEquals(
+                w1.getTask(false).get().getUnchecked(),
+                MutableList.of("a=b", "b=c"));
+    }
+
 }
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 03b14b9d6e..0d9616c1d9 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
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.core.workflow;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.api.entity.EntitySpec;
@@ -27,7 +26,6 @@ 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.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSizeTest.java 
b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSizeTest.java
index ec81b25d8c..917f0c1f7b 100644
--- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSizeTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSizeTest.java
@@ -70,9 +70,10 @@ public class WorkflowSizeTest extends 
BrooklynMgmtUnitTestSupport {
         createAppWithEffector(MutableList.of(
                 "let pc = ${param}",
                 "let map myMap = {}",
-                "transform param | prepend hello-",
+                "transform ${param} | prepend hello_",
+                "transform variable param | prepend hello-",
                 "let myMap.a = ${param}",
-                "let myMap.b = ${output}",
+                "let myMap.b = ${output}",  // comes from hello_ further up
                 "return ${myMap}"
         ));
 
@@ -95,7 +96,7 @@ public class WorkflowSizeTest extends 
BrooklynMgmtUnitTestSupport {
         sizes.forEach((k,v) -> { log.info("Sensor "+k+": "+v); });
         Asserts.assertThat(sizes.values().stream().reduce(0, (v0,v1)->v0+v1), 
result -> result < 20*1000);
 
-        // 100k payload now -> bumps sensor (json) size from 5k to 3MB (before 
any optimization)
+        // 100k payload -> bumps sensor (json) size from 5k to 3MB (before any 
optimization)
         // [xml persistence is less of an issue because it will use a shared 
reference]
         // removing output which is identical to the previous gives minor 
savings (in this test): 3380416 -> 3176074
         // removing scratch at workflow which matches a step reduces further: 
-> 2869522
@@ -107,7 +108,7 @@ public class WorkflowSizeTest extends 
BrooklynMgmtUnitTestSupport {
         app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), 
MutableMap.of("param", sampleData)).getUnchecked();
         sizes = getSensorSizes();
         sizes.forEach((k,v) -> { log.info("Sensor "+k+": "+v); });
-        Asserts.assertThat(sizes.values().stream().reduce(0, (v0,v1)->v0+v1), 
result -> result > 100*1000);
+        Asserts.assertThat(sizes.values().stream().reduce(0, (v0,v1)->v0+v1), 
result -> result > 100*1000 && result < 2*1000*1000);
     }
 
     protected Map<String,Integer> getSensorSizes() {
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowTransformTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowTransformTest.java
index 9d102cf6f6..af4746cd90 100644
--- 
a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowTransformTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowTransformTest.java
@@ -25,6 +25,7 @@ import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
+import 
org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep;
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableList;
@@ -74,6 +75,49 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
         return runWorkflowSteps(app, "transform "+args);
     }
 
+
+    @Test
+    public void testTransformShorthand() {
+        Function<String,Map<String,Object>> parse = shorthand -> {
+            TransformVariableWorkflowStep s = new 
TransformVariableWorkflowStep();
+            s.populateFromShorthand(shorthand);
+            return s.input;
+        };
+
+        Asserts.assertEquals(parse.apply("variable x"), MutableMap.of(
+                "variable", MutableMap.of("name", "x")));
+
+        Asserts.assertEquals(parse.apply("value ${x}"), MutableMap.of(
+                "value", "${x}"));
+
+        Asserts.assertEquals(parse.apply("x"), MutableMap.of(
+                "vv_auto", "x"));
+
+        Asserts.assertEquals(parse.apply("x = 1 | foo"), MutableMap.of(
+                "vv_auto", "x",
+                "value", "1",
+                "transform", "foo"));
+        Asserts.assertEquals(parse.apply("integer x = 1 | foo"), MutableMap.of(
+                "variable", MutableMap.of("type", "integer"),
+                "vv_auto", "x",
+                "value", "1",
+                "transform", "foo"));
+
+        Asserts.assertEquals(parse.apply("integer variable x | foo"), 
MutableMap.of(
+                "variable", MutableMap.of("name", "x", "type", "integer"),
+                "transform", "foo"));
+        Asserts.assertEquals(parse.apply("integer value ${x} | foo"), 
MutableMap.of(
+                "variable", MutableMap.of("type", "integer"),
+                "value", "${x}",
+                "transform", "foo"));
+
+        Asserts.assertFailsWith(() -> parse.apply("integer variable x = 99 | 
foo"),
+                e -> Asserts.expectedFailureContainsIgnoreCase(e, "Invalid 
shorthand expression for transform"));
+        Asserts.assertFailsWith(() -> parse.apply("integer value x = 99 | 
foo"),
+                e -> Asserts.expectedFailureContainsIgnoreCase(e, "Invalid 
shorthand expression for transform"));
+    }
+
+
     @Test
     public void testTransformTrim() throws Exception {
         String untrimmed = "Hello, World!   ";
@@ -140,7 +184,7 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
                     "    that's: okay",
                     "    ---",
                     "     key: value",
-                    "- transform s | yaml | return",
+                    "- transform ${s} | yaml | return",
                     "- return should not come here",
                 ""), "test").getTask(false).get().getUnchecked(),
                 MutableMap.of("key", "value"));
@@ -151,15 +195,31 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
         Asserts.assertEquals(WorkflowBasicTest.runWorkflow(app, Strings.lines(
                     "- step: let s",
                     "  value: \"key: Value\"",
-                    "- transform s | yaml | set y",
+                    "- transform ${s} | yaml | set y",
                     "- transform y.key2 = ${output.key} | to_upper_case",
-                    "- transform output.key | to_lower_case",  // output 
should still be the yaml map transformed from ${s}
-                    "- transform output | set y.key3",   // output passed in 
here will be 'value' from previous step
-                    "- transform value true | set y.key4",
-                    "- transform boolean value true | set y.key5",
+                    "- transform ${output.key} | to_lower_case",  // output 
should still be the yaml map transformed from ${s}
+                    "- transform ${output} | set y.key3",   // output passed 
in here will be 'value' from previous step
+                    "- transform true | set y.key4a",
+                    "- transform value true | set y.key4b",
+                    "- transform boolean value true | set y.key4c",
                     "- return ${y}",
-                ""), "test").getTask(false).get().getUnchecked(),
-                MutableMap.of("key", "Value", "key2", "VALUE", "key3", 
"value", "key4", "true", "key5", true));
+                ""), "test").getTask(true).get().getUnchecked(),
+                MutableMap.of("key", "Value", "key2", "VALUE", "key3", 
"value", "key4a", "true", "key4b", "true", "key4c", true));
+    }
+
+    @Test
+    public void testValueTransform() {
+        Asserts.assertEquals(WorkflowBasicTest.runWorkflow(app, Strings.lines(
+                "- let map m = { a: x }",
+                "- transform value ${m} | size"
+        ), "test").getTask(true).get().getUnchecked(), 1);
+
+//                "- let x = hello",
+//                "- transform value ${x} | append world"), 
"test").getTask(true).get().getUnchecked(), "helloworld");
+//                "- let apply_result = hello",
+//                "- transform value ${apply_result} | to_upper_case"
+//                , "- return ${apply_result}"
+//        ), "test").getTask(true).get().getUnchecked(), "HELLO");
     }
 
     @Test
@@ -168,7 +228,8 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
                     "let a = b",
                     "let b = c",
                     "let x = \"${\" ${a} \"}\"",
-                    "transform x | resolve_expression | return"
+                    "transform variable x | resolve_expression",
+                    "return ${x}"
                 ), "c");
 
         Asserts.assertEquals(runWorkflowSteps(

Reply via email to