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 <[email protected]>
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");
+ }
+
+}