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 d27d1720eb add 'slice' and 'remove' transforms to workflow
d27d1720eb is described below

commit d27d1720ebdf415699e7e23ea526670d8f7bbbaf
Author: Alex Heneveld <[email protected]>
AuthorDate: Thu May 25 11:02:27 2023 +0100

    add 'slice' and 'remove' transforms to workflow
    
    and tidy up transform step to allow supplying a value with no variable name
---
 .../workflow/steps/variables/TransformRemove.java  | 62 ++++++++++++++++
 .../workflow/steps/variables/TransformSlice.java   | 85 ++++++++++++++++++++++
 .../variables/TransformVariableWorkflowStep.java   | 26 ++++---
 .../core/workflow/WorkflowTransformTest.java       | 31 +++++++-
 4 files changed, 193 insertions(+), 11 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformRemove.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformRemove.java
new file mode 100644
index 0000000000..1af1e47fae
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformRemove.java
@@ -0,0 +1,62 @@
+/*
+ * 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.steps.variables;
+
+import com.google.common.reflect.TypeToken;
+import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class TransformRemove extends WorkflowTransformDefault {
+    private String removal;
+
+    @Override
+    protected void initCheckingDefinition() {
+        List<String> d = definition.subList(1, definition.size());
+        if (d.size()!=1) throw new IllegalArgumentException("Transform 
'remove' requires a single argument being the item (for a map) or index (for a 
list) to remove");
+        removal = d.get(0);
+    }
+
+    @Override
+    public Object apply(Object v) {
+        if (v instanceof Map) {
+            Map vm = MutableMap.copyOf((Map)v);
+            Object removalResolved = 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_RUNNING,
 removal);
+            vm.remove(removalResolved);
+            return vm;
+
+        } else if (v instanceof Iterable) {
+            List vl = MutableList.copyOf((Iterable) v);
+            Integer removalIndex = TransformSlice.resolveAs(removal, context, 
"Argument", true, Integer.class, "an integer");
+            if (removalIndex<0) removalIndex = vl.size() + removalIndex;
+            if (removalIndex<0 || removalIndex>vl.size()) throw new 
IllegalArgumentException("Invalid removal index "+removalIndex+"; list is size 
"+vl.size());
+            vl.remove((int)removalIndex);
+            return vl;
+
+        } else {
+            throw new IllegalArgumentException("Invalid value for remove 
transform; expects a map or list, not "+(v==null ? "null" : v.getClass()));
+        }
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformSlice.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformSlice.java
new file mode 100644
index 0000000000..f8b357a4ac
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformSlice.java
@@ -0,0 +1,85 @@
+/*
+ * 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.steps.variables;
+
+import com.google.common.reflect.TypeToken;
+import org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
+import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class TransformSlice extends WorkflowTransformDefault {
+    private String startIndex;
+    private String endIndex;
+
+    @Override
+    protected void initCheckingDefinition() {
+        List<String> d = MutableList.copyOf(definition.subList(1, 
definition.size()));
+        if (d.isEmpty()) throw new IllegalArgumentException("Transform 'slice' 
requires arguments: <start_index> [<end_index>]");
+        startIndex = d.remove(0);
+        if (!d.isEmpty()) endIndex = d.remove(0);
+        if (!d.isEmpty()) throw new IllegalArgumentException("Transform 
'slice' takes max 2 arguments: <start_index> [<end_index>]");
+    }
+
+    @Override
+    public Object apply(Object v) {
+        if (v instanceof String) {
+            List<Integer> codePoints = ((String) 
v).codePoints().boxed().collect(Collectors.toList());
+            return applyList(codePoints).stream()
+                    .collect(StringBuilder::new, 
StringBuilder::appendCodePoint, StringBuilder::append )
+                    .toString();
+        } else {
+            List list = 
context.resolveCoercingOnly(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_RUNNING,
 v, TypeToken.of(List.class));
+            if (list==null) throw new IllegalArgumentException("List to slice 
is null");
+            return applyList(list);
+        }
+    }
+
+    protected <T> List<T> applyList(List<T> v) {
+        List list = 
context.resolveCoercingOnly(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_RUNNING,
 v, TypeToken.of(List.class));
+        if (list==null) throw new IllegalArgumentException("List to slice is 
null");
+
+        Integer from = resolveAs(startIndex, context, "First argument 
start-index", true, Integer.class, "an integer");
+        Integer to = resolveAs(endIndex, context, "Second argument end-index", 
false, Integer.class, "an integer");
+
+        if (from<0) from = list.size() + from;
+        if (from<0 || from>list.size()) throw new 
IllegalArgumentException("Invalid start index "+from+"; list is size 
"+list.size());
+        if (to==null) to = list.size();
+        if (to<0) to = list.size() + to;
+        if (to<0 || to>list.size()) throw new 
IllegalArgumentException("Invalid end index "+to+"; list is size "+list.size());
+
+        if (to<from) throw new IllegalArgumentException("Invalid end index 
"+to+"; should be greater than or equal to start index "+from);
+        return list.subList(from, to);
+    }
+
+    static <T> T resolveAs(Object expression, WorkflowExecutionContext 
context, String errorPrefix, boolean failIfNull, Class<T> type, String 
typeName) {
+        T result = null;
+        try {
+            if (expression!=null) result = 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_RUNNING,
 expression, type);
+        } catch (Exception e) {
+            throw Exceptions.propagate(errorPrefix + " must be "+typeName+" 
("+expression+")", e);
+        }
+        if (failIfNull && result==null) throw new 
IllegalArgumentException(errorPrefix+" is required");
+        return result;
+    }
+
+}
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 eef4cb2504..d27d182cee 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
@@ -54,8 +54,8 @@ 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...} ] ]";
+            "[ [ [ ${variable.type} ] [ ?${value_is_initial} \"value\" ] 
${variable.name} " +
+            "[ [ \"=\" ${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");
@@ -71,9 +71,6 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
     @Override
     public void validateStep(@Nullable ManagementContext mgmt, @Nullable 
WorkflowExecutionContext workflow) {
         super.validateStep(mgmt, workflow);
-        if (!input.containsKey(VARIABLE.getName())) {
-            throw new IllegalArgumentException("Variable name is required");
-        }
         if (!input.containsKey(TRANSFORM.getName())) {
             throw new IllegalArgumentException("Transform is required");
         }
@@ -82,9 +79,7 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
     @Override
     protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) {
         TypedValueToSet variable = context.getInput(VARIABLE);
-        if (variable==null) throw new IllegalArgumentException("Variable name 
is required");
-        String name = 
context.resolve(WorkflowExpressionResolution.WorkflowExpressionStage.STEP_INPUT,
 variable.name, String.class);
-        if (Strings.isBlank(name)) throw new 
IllegalArgumentException("Variable name is required");
+        String name;
 
         Object transformO = context.getInput(TRANSFORM);
         if (!(transformO instanceof Iterable)) transformO = 
MutableList.of(transformO);
@@ -96,11 +91,20 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
 
         Object v;
         if (input.containsKey(VALUE.getName())) {
-            if (Boolean.TRUE.equals(context.getInput(VALUE_IS_INITIAL))) throw 
new IllegalArgumentException("Cannot specifiy value_is_initial (keyword 
\"value\") with an = value");
+            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");
+            }
 
             v = input.get(VALUE.getName());
-            if (Strings.isNonBlank(variable.type)) transforms.add("type 
"+variable.type);
+            if (variable!=null && Strings.isNonBlank(variable.type)) 
transforms.add("type "+variable.type);
+
         } else {
+            if (variable==null || Strings.isBlank(variable.name)) throw new 
IllegalArgumentException("Variable name is required");
+
             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
@@ -176,6 +180,8 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
     static {
         TRANSFORMATIONS.put("trim", () -> new TransformTrim());
         TRANSFORMATIONS.put("merge", () -> new TransformMerge());
+        TRANSFORMATIONS.put("slice", () -> new TransformSlice());
+        TRANSFORMATIONS.put("remove", () -> new TransformRemove());
         TRANSFORMATIONS.put("json", () -> new TransformJsonish(true, false, 
false));
         TRANSFORMATIONS.put("yaml", () -> new TransformJsonish(false, true, 
false));
         TRANSFORMATIONS.put("bash", () -> new TransformJsonish(true, false, 
true));
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 58ccbdbab1..0656127ead 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
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.core.workflow;
 
+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.Task;
@@ -31,11 +32,13 @@ 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.text.StringEscapes;
 import org.apache.brooklyn.util.text.Strings;
 import org.testng.annotations.Test;
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 public class WorkflowTransformTest extends BrooklynMgmtUnitTestSupport {
 
@@ -264,7 +267,6 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
         Asserts.assertEquals(result, MutableMap.of("key", "Value", "key2", 
"VALUE", "key3", "value", "key4", "true", "key5", true));
     }
 
-
     @Test
     public void testResolveTransform() {
         loadTypes();
@@ -279,4 +281,31 @@ public class WorkflowTransformTest extends 
BrooklynMgmtUnitTestSupport {
         Asserts.assertEquals(result, "c");
     }
 
+    @Test
+    public void testSliceAndRemoveTransform() {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        Function<String,Object> transform = tx -> 
WorkflowBasicTest.runWorkflow(app, Strings.lines("- "+tx), 
"test").getTask(false).get().getUnchecked();
+
+        // slice list
+        Asserts.assertEquals(transform.apply("transform value ['a','bb','ccc'] 
| type list | slice 1"), MutableList.of("bb", "ccc"));
+        Asserts.assertEquals(transform.apply("transform value ['a','bb','ccc'] 
| type list | slice 1 -1"), MutableList.of("bb"));
+        Asserts.assertEquals(transform.apply("transform value ['a','bb','ccc'] 
| type list | slice -1"), MutableList.of("ccc"));
+
+        // slice string
+        Asserts.assertEquals(transform.apply("transform value abc | slice 1"), 
"bc");
+
+        // remove list
+        Asserts.assertEquals(transform.apply("transform value ['a','bb','ccc'] 
| type list | remove 1"), MutableList.of("a", "ccc"));
+
+        // remove map
+        Asserts.assertEquals(transform.apply("step: transform\n" +
+                "  transform: type map | remove b\n" +
+                "  value:\n" +
+                "    a: 1\n" +
+                "    b: 2"), MutableMap.of("a", 1));
+    }
+
 }

Reply via email to