This is an automated email from the ASF dual-hosted git repository.

duncangrant 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 51ee4a1c21 Adds Workflow Replace Transform
     new 1098e61808 Merge pull request #1392 from 
nakomis/workflow-transform-regex
51ee4a1c21 is described below

commit 51ee4a1c218ac497dbc4e0edb9251a3b35c6bf90
Author: Martin Harris <git...@nakomis.com>
AuthorDate: Wed Apr 19 11:03:19 2023 +0100

    Adds Workflow Replace Transform
---
 .../workflow/steps/variables/TransformReplace.java | 196 ++++++++++++++++++++
 .../variables/TransformVariableWorkflowStep.java   |   3 +-
 .../steps/variables/WorkflowTransformDefault.java  |   5 +
 .../variables/WorkflowTransformWithContext.java    |   2 +
 .../core/workflow/WorkflowTransformTest.java       | 202 +++++++++++++++++++++
 5 files changed, 407 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformReplace.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformReplace.java
new file mode 100644
index 0000000000..65b9b07fc1
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/TransformReplace.java
@@ -0,0 +1,196 @@
+/*
+ * 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 org.apache.brooklyn.core.workflow.ShorthandProcessor;
+import org.apache.brooklyn.core.workflow.WorkflowExecutionContext;
+import org.apache.brooklyn.util.guava.Maybe;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class TransformReplace extends WorkflowTransformDefault {
+    boolean regex;
+    boolean glob;
+    boolean literal;
+    boolean all;
+    String patternToMatch;
+    String replacement;
+
+    String SHORTHAND = "\"replace\" [ ?${all} \"all\" ] [ ?${regex} \"regex\" 
] [ ?${glob} \"glob\" ] [ ?${literal} \"literal\" ] ${patternToMatch} 
${replacement}";
+
+    @Override
+    public void init(WorkflowExecutionContext context, List<String> 
definition, String transformDef) {
+        super.init(context, null);
+
+        Maybe<Map<String, Object>> maybeResult = new 
ShorthandProcessor(SHORTHAND)
+                .withFinalMatchRaw(true)
+                .withFailOnMismatch(true)
+                .process(transformDef);
+
+        if (maybeResult.isPresent()) {
+            // TODO: Ability to parse Brooklyn DSL
+            Map<String, Object> result = maybeResult.get();
+            all = Boolean.TRUE.equals(result.get("all"));
+            regex = Boolean.TRUE.equals(result.get("regex"));
+            glob = Boolean.TRUE.equals(result.get("glob"));
+            literal = Boolean.TRUE.equals(result.get("literal"));
+            patternToMatch = String.valueOf(result.get("patternToMatch"));
+            replacement = String.valueOf(result.get("replacement"));
+
+            int replaceTypeCount = (regex?1:0) + (glob?1:0) + (literal?1:0);
+
+            if (replaceTypeCount > 1) {
+                throw new IllegalArgumentException("Only one of regex, glob, 
and literal can be set");
+            } else if (replaceTypeCount == 0) {
+                // Set the default if none provided
+                literal = true;
+            }
+        } else {
+            throw new IllegalArgumentException("Expression must be of the form 
'replace [all] [regex|glob|literal] patternToMatch replacement'");
+        }
+    }
+
+    @Override
+    public Object apply(Object o) {
+        if (o == null)
+            return null;
+        if (!(o instanceof String)) {
+            throw new IllegalArgumentException("Expression must be of the form 
replace [regex|glob|literal] pattern_to_match replacement");
+        }
+        String input = (String)o;
+
+        if (regex) {
+            return all ? input.replaceAll(patternToMatch, replacement)
+                    : input.replaceFirst(patternToMatch, replacement);
+        }
+        if (glob) {
+            String globToRegex = convertGlobToRegex(patternToMatch);
+
+            return all ? input.replaceAll(globToRegex, replacement)
+                    : input.replaceFirst(globToRegex, replacement);
+        }
+        if (literal) {
+            return all ? Pattern.compile(patternToMatch, 
Pattern.LITERAL).matcher(input).replaceAll(replacement)
+                    : Pattern.compile(patternToMatch, 
Pattern.LITERAL).matcher(input).replaceFirst(replacement);
+        }
+        // Should never get here
+        throw new IllegalArgumentException("Expression must be of the form 
replace [regex|glob|literal] pattern_to_match replacement");
+    }
+
+    /**
+     * Converts a standard POSIX Shell globbing pattern into a regular 
expression
+     *
+     * Copied from Neil Traft's answer in 
https://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns/17369948#17369948
+     * See public domain statement in comments on the above
+     *
+     * TODO: Want to use library utility, but cant' find a suitable one other 
than the now-retired Apache Jakarta ORO
+     * https://jakarta.apache.org/oro/
+     *
+     */
+    private String convertGlobToRegex(String pattern) {
+        StringBuilder sb = new StringBuilder(pattern.length());
+        int inGroup = 0;
+        int inClass = 0;
+        int firstIndexInClass = -1;
+        char[] arr = pattern.toCharArray();
+        for (int i = 0; i < arr.length; i++) {
+            char ch = arr[i];
+            switch (ch) {
+                case '\\':
+                    if (++i >= arr.length) {
+                        sb.append('\\');
+                    } else {
+                        char next = arr[i];
+                        switch (next) {
+                            case ',':
+                                // escape not needed
+                                break;
+                            case 'Q':
+                            case 'E':
+                                // extra escape needed
+                                sb.append('\\');
+                            default:
+                                sb.append('\\');
+                        }
+                        sb.append(next);
+                    }
+                    break;
+                case '*':
+                    if (inClass == 0)
+                        sb.append(".*");
+                    else
+                        sb.append('*');
+                    break;
+                case '?':
+                    if (inClass == 0)
+                        sb.append('.');
+                    else
+                        sb.append('?');
+                    break;
+                case '[':
+                    inClass++;
+                    firstIndexInClass = i+1;
+                    sb.append('[');
+                    break;
+                case ']':
+                    inClass--;
+                    sb.append(']');
+                    break;
+                case '.':
+                case '(':
+                case ')':
+                case '+':
+                case '|':
+                case '^':
+                case '$':
+                case '@':
+                case '%':
+                    if (inClass == 0 || (firstIndexInClass == i && ch == '^'))
+                        sb.append('\\');
+                    sb.append(ch);
+                    break;
+                case '!':
+                    if (firstIndexInClass == i)
+                        sb.append('^');
+                    else
+                        sb.append('!');
+                    break;
+                case '{':
+                    inGroup++;
+                    sb.append('(');
+                    break;
+                case '}':
+                    inGroup--;
+                    sb.append(')');
+                    break;
+                case ',':
+                    if (inGroup > 0)
+                        sb.append('|');
+                    else
+                        sb.append(',');
+                    break;
+                default:
+                    sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+}
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 a8d0c05fb8..f95d20cba6 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
@@ -137,7 +137,7 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
         }
         if (t==null) throw new IllegalStateException("Unknown transform 
'"+transformType+"'");
         WorkflowTransformWithContext ta = transformOf(t);
-        ta.init(context.getWorkflowExectionContext(), transformWords);
+        ta.init(context.getWorkflowExectionContext(), transformWords, 
transformDef);
         return ta;
     }
 
@@ -148,6 +148,7 @@ public class TransformVariableWorkflowStep extends 
WorkflowStepDefinition {
         TRANSFORMATIONS.put("json", () -> new TransformJsonish(true, false, 
false));
         TRANSFORMATIONS.put("yaml", () -> new TransformJsonish(false, true, 
false));
         TRANSFORMATIONS.put("bash", () -> new TransformJsonish(true, false, 
true));
+        TRANSFORMATIONS.put("replace", () -> new TransformReplace());
         TRANSFORMATIONS.put("wait", () -> new TransformWait());
         TRANSFORMATIONS.put("type", () -> new TransformType());
         TRANSFORMATIONS.put("first", () -> x -> {
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformDefault.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformDefault.java
index 3b7de118dc..386f4fa74f 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformDefault.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformDefault.java
@@ -25,6 +25,11 @@ import java.util.List;
 public abstract class WorkflowTransformDefault implements 
WorkflowTransformWithContext {
     protected WorkflowExecutionContext context;
 
+    @Override
+    public void init(WorkflowExecutionContext context, List<String> 
definition, String transformDef) {
+        init(context, definition);
+    }
+
     @Override
     public void init(WorkflowExecutionContext context, List<String> 
definition) {
         this.context = context;
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformWithContext.java
 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformWithContext.java
index ccc9bb1788..4819feacd1 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformWithContext.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/variables/WorkflowTransformWithContext.java
@@ -25,5 +25,7 @@ import java.util.List;
 public interface WorkflowTransformWithContext extends WorkflowTransform {
     void init(WorkflowExecutionContext context, List<String> definition);
 
+    void init(WorkflowExecutionContext context, List<String> definition, 
String transformDef);
+
     boolean isResolver();
 }
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
new file mode 100644
index 0000000000..0e66f257a5
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowTransformTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
+import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep;
+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.testng.annotations.Test;
+
+import java.util.List;
+
+public class WorkflowTransformTest extends BrooklynMgmtUnitTestSupport {
+
+    protected void loadTypes() {
+        WorkflowBasicTest.addWorkflowStepTypes(mgmt);
+    }
+
+    @Test
+    public void testTransformTrim() throws Exception {
+        loadTypes();
+
+        String untrimmed = "Hello, World!   ";
+        String trimmed = untrimmed.trim();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("let mystring = '"+untrimmed+"'")
+                        .append("transform mytrimmedstring = ${mystring} | 
trim")
+                        .append("return ${mytrimmedstring}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, trimmed);
+    }
+
+    @Test
+    public void testTransformRegex() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'silly world' | replace regex 
l. k")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "siky world");
+    }
+
+    @Test
+    public void testTransformAllRegex() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'silly world' | replace all 
regex l. k")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "siky work");
+    }
+
+    @Test
+    public void testTransformRegexWithBackslash() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'abc/def/ghi' | replace regex 
'c/d' XXX")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "abXXXef/ghi");
+    }
+
+    @Test
+    public void testTransformRegexWithSpace() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'abc def ghi' | replace regex 
'c d' XXX")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "abXXXef ghi");
+    }
+
+    @Test
+    public void testTransformLiteral() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'abc.*def ghi' | replace 
literal c.*d XXX")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "abXXXef ghi");
+    }
+
+    @Test
+    public void testTransformGlob() throws Exception {
+        loadTypes();
+
+        BasicApplication app = 
mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class));
+
+        WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
+                .configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
+                .configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, 
MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
+                .configure(WorkflowEffector.STEPS, MutableList.<Object>of()
+                        .append("transform x = 'abc.*def ghi' | replace glob 
c*e XXX")
+                        .append("return ${x}")
+                )
+        );
+        eff.apply((EntityLocal)app);
+
+        Task<?> invocation = 
app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
+        Object result = invocation.getUnchecked();
+        Asserts.assertNotNull(result);
+        Asserts.assertEquals(result, "abXXXf ghi");
+    }
+
+}

Reply via email to