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));
+ }
+
}