add if step, and improve deep workflow partial resolution for conditions
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
index 7779fc6..b7ba499 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowYamlTest.java
@@ -689,7 +689,7 @@
@Test
public void testConditionBadSerialization() throws Exception {
Asserts.assertFailsWith(() -> doTestCondition("- regex: .*oh no.*"),
- e -> Asserts.expectedFailureContainsIgnoreCase(e, "unresolveable", "regex"));
+ Asserts.expectedFailureContainsIgnoreCase("unresolvable", "regex"));
}
@Test
public void testBadExpressionAllowedInCondition() throws Exception {
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java
index 72b5477..a26febb 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExecutionContext.java
@@ -709,7 +709,7 @@
}
/** as {@link #resolve(WorkflowExpressionResolution.WorkflowExpressionStage, Object, TypeToken)},
- * but returning DSL/supplier for values (so the indication of their dynamic nature is preserved, even if the value returned by it is resolved;
+ * but returning DSL/supplier for dynamic values (so the indication of their dynamic nature is preserved, even if the value returned by it is resolved;
* this is needed e.g. for conditions which treat dynamic expressions differently to explicit values) */
public <T> T resolveWrapped(WorkflowExpressionResolution.WorkflowExpressionStage stage, Object expression, TypeToken<T> type, WorkflowExpressionResolution.WrappingMode wrappingMode) {
return new WorkflowExpressionResolution(this, stage, false, wrappingMode).resolveWithTemplates(expression, type);
@@ -1597,8 +1597,19 @@
log.debug(prefix + "no further steps: Workflow completed");
}
} else if (specialNext instanceof String) {
+ boolean isInLocalSubworkflow = getParent()!=null && getParent().currentStepInstance!=null && Boolean.TRUE.equals(getParent().currentStepInstance.isLocalSubworkflow);
+ if (isInLocalSubworkflow && Boolean.TRUE.equals(currentStepInstance.nextIsReturn)) {
+ // parent of local subworkflow should return also
+ getParent().currentStepInstance.next = specialNext;
+ }
+
String explicitNext = (String)specialNext;
Maybe<Pair<Integer, Boolean>> nextResolved = getIndexOfStepId(explicitNext);
+ if (nextResolved.isAbsent() && isInLocalSubworkflow) {
+ // if in subworkflow, you can goto something in the outer workflow, if not found here
+ getParent().currentStepInstance.next = specialNext;
+ nextResolved = getIndexOfStepId(STEP_TARGET_NAME_FOR_END);
+ }
if (nextResolved.isAbsent()) {
log.warn(prefix + (inferredNext ? "inferred" : "explicit") + " next step '"+explicitNext+"' not found (failing)");
// throw
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 ab4836f..c8e32ab 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
@@ -18,9 +18,20 @@
*/
package org.apache.brooklyn.core.workflow;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
import com.google.common.annotations.Beta;
import com.google.common.reflect.TypeToken;
-import freemarker.core.InvalidReferenceException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
@@ -49,12 +60,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.time.Instant;
-import java.util.*;
-import java.util.function.BiFunction;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
public class WorkflowExpressionResolution {
public static ConfigKey<BiFunction<String,WorkflowExpressionResolution,Object>> WORKFLOW_CUSTOM_INTERPOLATION_FUNCTION = ConfigKeys.newConfigKey(new TypeToken<BiFunction<String,WorkflowExpressionResolution,Object>>() {}, "workflow.custom_interpolation_function");
@@ -82,14 +87,14 @@
private final WrappingMode wrappingMode;
public static class WrappingMode {
- public final boolean wrapResolvedStrings;
+ public final boolean wrapResolvedValues;
public final boolean deferThrowingError;
public final boolean deferAndRetryErroneousExpressions;
public final boolean deferBrooklynDsl;
public final boolean deferInterpolation;
- protected WrappingMode(boolean wrapResolvedStrings, boolean deferThrowingError, boolean deferAndRetryErroneousExpressions, boolean deferBrooklynDsl, boolean deferInterpolation) {
- this.wrapResolvedStrings = wrapResolvedStrings;
+ protected WrappingMode(boolean wrapResolvedValues, boolean deferThrowingError, boolean deferAndRetryErroneousExpressions, boolean deferBrooklynDsl, boolean deferInterpolation) {
+ this.wrapResolvedValues = wrapResolvedValues;
this.deferThrowingError = deferThrowingError;
this.deferAndRetryErroneousExpressions = deferAndRetryErroneousExpressions;
this.deferBrooklynDsl = deferBrooklynDsl;
@@ -375,14 +380,17 @@
}
}
- AllowBrooklynDslMode defaultAllowBrooklynDsl = AllowBrooklynDslMode.ALL;
+ AllowBrooklynDslMode defaultAllowBrooklynDsl = null;
+ //AllowBrooklynDslMode.ALL;
public void setDefaultAllowBrooklynDsl(AllowBrooklynDslMode defaultAllowBrooklynDsl) {
this.defaultAllowBrooklynDsl = defaultAllowBrooklynDsl;
}
public AllowBrooklynDslMode getDefaultAllowBrooklynDsl() {
- return defaultAllowBrooklynDsl;
+ if (defaultAllowBrooklynDsl!=null) return defaultAllowBrooklynDsl;
+ if (wrappingMode.deferBrooklynDsl) return AllowBrooklynDslMode.NONE;
+ return AllowBrooklynDslMode.ALL;
}
public <T> T resolveWithTemplates(Object expression, TypeToken<T> type) {
@@ -550,14 +558,36 @@
throw new WorkflowVariableRecursiveReference("Reference exceeded max depth 100: " + RESOLVE_STACK.getAll(false).stream().map(p -> "" + p.object).collect(Collectors.joining("->")));
}
- if (expression instanceof String) return processTemplateExpressionString((String) expression, allowBrooklynDsl);
- if (expression instanceof Map) return processTemplateExpressionMap((Map) expression, allowBrooklynDsl);
- if (expression instanceof Collection)
- return processTemplateExpressionCollection((Collection) expression, allowBrooklynDsl);
- if (expression == null || Boxing.isPrimitiveOrBoxedObject(expression)) return expression;
- // otherwise resolve DSL
- return allowBrooklynDsl.isAllowedHere() ? resolveDsl(expression) : expression;
+ Object result;
+ if (expression instanceof String) result = processTemplateExpressionString((String) expression, allowBrooklynDsl);
+ else if (expression instanceof Map) result = processTemplateExpressionMap((Map) expression, allowBrooklynDsl);
+ else if (expression instanceof Collection)
+ result = processTemplateExpressionCollection((Collection) expression, allowBrooklynDsl);
+ else if (expression == null || Boxing.isPrimitiveOrBoxedObject(expression)) result = expression;
+ else {
+ // otherwise resolve DSL
+ result = allowBrooklynDsl.isAllowedHere() ? resolveDsl(expression) : expression;
+ if (wrappingMode.wrapResolvedValues && !Objects.equals(result, expression) && !(result instanceof DeferredSupplier)) {
+ result = WrappedResolvedExpression.ifNonDeferred(expression, result);
+ }
+ }
+ return result;
+
+ } catch (Exception e) {
+ Exception e2 = e;
+ if (wrappingMode.deferAndRetryErroneousExpressions) {
+ return WrappedUnresolvedExpression.ofExpression(expression, this, allowBrooklynDsl);
+ }
+ if (!allowWaiting && Exceptions.isCausedByInterruptInAnyThread(e)) {
+ e2 = new IllegalArgumentException("Expression value '"+expression+"' unavailable and not permitted to wait: "+ Exceptions.collapseText(e), e);
+ }
+ if (wrappingMode.deferThrowingError) {
+ // in wrapped value mode, errors don't throw until accessed, and when used in conditions they can be tested as absent
+ return WrappedResolvedExpression.ofError(expression, new ResolutionFailureTreatedAsAbsent.ResolutionFailureTreatedAsAbsentDefaultException(e2));
+ } else {
+ throw Exceptions.propagate(e2);
+ }
} finally {
if (entry != null) RESOLVE_STACK.pop(entry);
}
@@ -602,41 +632,28 @@
}
public Object processTemplateExpressionString(String expression, AllowBrooklynDslMode allowBrooklynDsl) {
- if (expression==null) return null;
- if (expression.startsWith("$brooklyn:") && allowBrooklynDsl.isAllowedHere()) {
- if (wrappingMode.deferBrooklynDsl) {
- return WrappedUnresolvedExpression.ofExpression(expression, this, allowBrooklynDsl);
- }
- Object expressionTemplateResolved = processTemplateExpressionString(expression, AllowBrooklynDslMode.NONE);
- // resolve interpolation before brooklyn DSL, so brooklyn DSL can be passed interpolated vars like workflow scratch;
- // this means $brooklyn bits that return interpolated strings do not have their interpolation evaluated, which is probably sensible;
- // and $brooklyn cannot be used inside an interpolated string, which is okay.
- Object expressionTemplateAndDslResolved = resolveDsl(expressionTemplateResolved);
- return expressionTemplateAndDslResolved;
- }
-
Object result;
-
- boolean ourWait = interruptSetIfNeededToPreventWaiting();
+ boolean ourWait = false;
try {
+ if (expression==null) return null;
+ if (expression.startsWith("$brooklyn:") && allowBrooklynDsl.isAllowedHere()) {
+ if (wrappingMode.deferBrooklynDsl) {
+ return WrappedUnresolvedExpression.ofExpression(expression, this, allowBrooklynDsl);
+ }
+ Object expressionTemplateResolved = processTemplateExpressionString(expression, AllowBrooklynDslMode.NONE);
+ // resolve interpolation before brooklyn DSL, so brooklyn DSL can be passed interpolated vars like workflow scratch;
+ // this means $brooklyn bits that return interpolated strings do not have their interpolation evaluated, which is probably sensible;
+ // and $brooklyn cannot be used inside an interpolated string, which is okay.
+ Object expressionTemplateAndDslResolved = resolveDsl(expressionTemplateResolved);
+ return expressionTemplateAndDslResolved;
+ }
+
+ ourWait = interruptSetIfNeededToPreventWaiting();
BiFunction<String, WorkflowExpressionResolution, Object> fn = context.getManagementContext().getScratchpad().get(WORKFLOW_CUSTOM_INTERPOLATION_FUNCTION);
if (fn!=null) result = fn.apply(expression, this);
else result = TemplateProcessor.processTemplateContentsForWorkflow("workflow", expression,
newWorkflowFreemarkerModel(), true, false, errorMode);
- } catch (Exception e) {
- Exception e2 = e;
- if (wrappingMode.deferAndRetryErroneousExpressions) {
- return WrappedUnresolvedExpression.ofExpression(expression, this, allowBrooklynDsl);
- }
- if (!allowWaiting && Exceptions.isCausedByInterruptInAnyThread(e)) {
- e2 = new IllegalArgumentException("Expression value '"+expression+"' unavailable and not permitted to wait: "+ Exceptions.collapseText(e), e);
- }
- if (wrappingMode.deferThrowingError) {
- // in wrapped value mode, errors don't throw until accessed, and when used in conditions they can be tested as absent
- return WrappedResolvedExpression.ofError(expression, new ResolutionFailureTreatedAsAbsent.ResolutionFailureTreatedAsAbsentDefaultException(e2));
- } else {
- throw Exceptions.propagate(e2);
- }
+
} finally {
if (ourWait) interruptClear();
}
@@ -647,14 +664,14 @@
return WrappedUnresolvedExpression.ofExpression(expression, this, allowBrooklynDsl);
}
if (wrappingMode.deferBrooklynDsl) {
- return new WrappedResolvedExpression<Object>(expression, result);
+ return new WrappedResolvedExpression<>(expression, result);
}
// we try, but don't guarantee, that DSL expressions aren't re-resolved, ie $brooklyn:literal("$brooklyn:literal(\"x\")") won't return x;
// this block will return a supplier
result = processDslComponents(result);
- if (wrappingMode.wrapResolvedStrings) {
- return new WrappedResolvedExpression<Object>(expression, result);
+ if (wrappingMode.wrapResolvedValues) {
+ return new WrappedResolvedExpression<>(expression, result);
}
}
@@ -702,19 +719,23 @@
}
public static class WrappedResolvedExpression<T> implements DeferredSupplier<T> {
- String expression;
+ Object expression;
T value;
Throwable error;
public WrappedResolvedExpression() {}
- public WrappedResolvedExpression(String expression, T value) {
+ public WrappedResolvedExpression(Object expression, T value) {
this.expression = expression;
this.value = value;
}
- public static WrappedResolvedExpression ofError(String expression, Throwable error) {
+ public static WrappedResolvedExpression ofError(Object expression, Throwable error) {
WrappedResolvedExpression result = new WrappedResolvedExpression(expression, null);
result.error = error;
return result;
}
+ public static <T> DeferredSupplier<T> ifNonDeferred(Object expression, T value) {
+ if (value instanceof DeferredSupplier) return (DeferredSupplier<T>) value;
+ return new WrappedResolvedExpression(expression, value);
+ }
@Override
public T get() {
@@ -723,7 +744,7 @@
}
return value;
}
- public String getExpression() {
+ public Object getExpression() {
return expression;
}
public Throwable getError() {
@@ -734,16 +755,16 @@
public static class WrappedUnresolvedExpression implements DeferredSupplier<Object> {
@Deprecated @Beta // might re-introduce but for now needs to cache workflow context -- via resolver -- so discouraged
- public static WrappedUnresolvedExpression ofExpression(String expression, WorkflowExpressionResolution resolver, AllowBrooklynDslMode dslMode) {
+ public static WrappedUnresolvedExpression ofExpression(Object expression, WorkflowExpressionResolution resolver, AllowBrooklynDslMode dslMode) {
return new WrappedUnresolvedExpression(expression, resolver, dslMode);
}
- protected WrappedUnresolvedExpression(String expression, WorkflowExpressionResolution resolver, AllowBrooklynDslMode dslMode) {
+ protected WrappedUnresolvedExpression(Object expression, WorkflowExpressionResolution resolver, AllowBrooklynDslMode dslMode) {
this.expression = expression;
this.resolver = resolver;
this.dslMode = dslMode;
}
- String expression;
+ Object expression;
WorkflowExpressionResolution resolver;
AllowBrooklynDslMode dslMode;
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 25ca27e..3a5ab6b 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
@@ -96,10 +96,15 @@
@JsonIgnore
public DslPredicates.DslPredicate getConditionResolved(WorkflowStepInstanceExecutionContext context) {
+ return getConditionResolved(context, getConditionRaw());
+ }
+
+ @JsonIgnore
+ public DslPredicates.DslPredicate getConditionResolved(WorkflowStepInstanceExecutionContext context, Object conditionRaw) {
try {
- return context.context.resolveCondition(getConditionRaw());
+ return context.context.resolveCondition(conditionRaw);
} catch (Exception e) {
- throw Exceptions.propagateAnnotated("Unresolveable condition (" + getConditionRaw() + ")", e);
+ throw Exceptions.propagateAnnotated("Unresolvable condition (" + getConditionRaw() + ")", e);
}
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java
index db8543f..e1c6df8 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepInstanceExecutionContext.java
@@ -81,6 +81,8 @@
transient WorkflowExecutionContext context;
// replay instructions or a string explicit next step identifier
public Object next;
+ public Boolean nextIsReturn;
+ public Boolean isLocalSubworkflow;
/** Return any error we are handling, if the step is in an error handler,
* or an unhandled error if the step is not in an error handler,
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepResolution.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepResolution.java
index c08b1d2..358687a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepResolution.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowStepResolution.java
@@ -34,6 +34,7 @@
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.SubWorkflowStep;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
@@ -113,20 +114,14 @@
public List<WorkflowStepDefinition> resolveSubSteps(String scope, List<Object> subSteps) {
List<WorkflowStepDefinition> result = MutableList.of();
if (subSteps!=null) {
- // it's useful to allow subworkflows
- // XXX also useful to compress if a long-winded syntax was used
-// if (subSteps.size()==1) {
-// WorkflowStepDefinition subStepResolved = resolveStep(mgmt, Iterables.getOnlyElement(subSteps));
-// if (subStepResolved instanceof SubWorkflowStep && ((SubWorkflowStep)subStepResolved).isSimpleListOfStepsOnly()) {
-// return resolveSubSteps(mgmt, scope, ((SubWorkflowStep)subStepResolved).peekSteps());
-// }
-// }
subSteps.forEach(subStep -> {
WorkflowStepDefinition subStepResolved = resolveStep(subStep);
if (subStepResolved.getId() != null)
throw new IllegalArgumentException("Sub steps for "+scope+" are not permitted to have IDs: " + subStep);
-// if (subStepResolved instanceof CustomWorkflowStep && ((CustomWorkflowStep)subStepResolved).peekSteps()!=null)
-// throw new IllegalArgumentException("Sub steps for "+scope+" are not permitted to run sub-workflows: " + subStep);
+ // don't allow foreach and workflow with target, but do allow subworkflow and if
+ if (subStepResolved instanceof CustomWorkflowStep && !(subStepResolved instanceof SubWorkflowStep) &&
+ ((CustomWorkflowStep)subStepResolved).peekSteps()!=null)
+ throw new IllegalArgumentException("Sub steps for "+scope+" are not permitted to run custom or foreach sub-workflows: " + subStep);
result.add(subStepResolved);
});
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java
new file mode 100644
index 0000000..0fcdfdd
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/IfWorkflowStep.java
@@ -0,0 +1,101 @@
+/*
+ * 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.flow;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.reflect.TypeToken;
+import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WorkflowExpressionStage;
+import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WrappedResolvedExpression;
+import org.apache.brooklyn.core.workflow.WorkflowExpressionResolution.WrappingMode;
+import org.apache.brooklyn.core.workflow.WorkflowStepInstanceExecutionContext;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.predicates.DslPredicates.DslPredicate;
+import org.apache.brooklyn.util.core.predicates.DslPredicates.WhenPresencePredicate;
+
+public class IfWorkflowStep extends SubWorkflowStep {
+
+ // more conditions could be done in future, or (probably better) we bake it in to the interpolation langauge
+ public static final String SHORTHAND = "${condition_target} [ \"==\" ${condition_equals} ] [ \" then \" ${step...} ]";
+
+ public static final String SHORTHAND_TYPE_NAME_DEFAULT = "if";
+
+ protected boolean isInternalClassNotExtendedAndUserAllowedToSetMostThings(String typeBestGuess) {
+ return !isRegisteredTypeExtensionToClass(IfWorkflowStep.class, SHORTHAND_TYPE_NAME_DEFAULT, typeBestGuess);
+ }
+
+ Object condition_target;
+ Object condition_equals;
+
+ @Override
+ public void populateFromShorthand(String value) {
+ if (input==null) input = MutableMap.of();
+ populateFromShorthandTemplate(SHORTHAND, value, true, true);
+
+ if (input.containsKey("step")) {
+ Object step = input.remove("step");
+ if (this.steps!=null) throw new IllegalArgumentException("Cannot set step in shorthand and steps in body");
+ this.steps = MutableList.of(step);
+ } else if (steps==null) throw new IllegalArgumentException("'if' step requires a step or steps");
+
+ if (input.containsKey("condition_target")) {
+ if (this.condition!=null) throw new IllegalArgumentException("Cannot set condition in shorthand and in body");
+ this.condition_target = input.remove("condition_target");
+ if (input.containsKey("condition_equals")) {
+ this.condition_equals = input.remove("condition_equals");
+ }
+ } else if (condition == null) throw new IllegalArgumentException("'if' step requires a condition");
+ }
+
+ @Override
+ public Object getConditionRaw() {
+ // not accurate, but not used
+ return super.getConditionRaw();
+ }
+
+ @JsonIgnore
+ @Override
+ public DslPredicate getConditionResolved(WorkflowStepInstanceExecutionContext context) {
+ Map<String, Object> raw = MutableMap.of("target", getConditionRaw());
+ Object conditionConstructed = this.condition;
+ if (conditionConstructed==null) {
+ Object k = context.getWorkflowExectionContext().resolveWrapped(
+ WorkflowExpressionStage.STEP_RUNNING, condition_target, TypeToken.of(Object.class),
+ WrappingMode.WRAPPED_RESULT_DEFER_THROWING_ERROR_BUT_NO_RETRY);
+ Map c = MutableMap.of("target", WrappedResolvedExpression.ifNonDeferred(condition_target, k));
+
+ if (condition_equals !=null) {
+ Object v = context.getWorkflowExectionContext().resolveWrapped(
+ WorkflowExpressionStage.STEP_RUNNING, condition_equals, TypeToken.of(Object.class),
+ WrappingMode.WRAPPED_RESULT_DEFER_THROWING_ERROR_BUT_NO_RETRY);
+ c.put("check", WrappedResolvedExpression.ifNonDeferred(condition_equals, v));
+ c.put("assert", MutableMap.of("when", WhenPresencePredicate.PRESENT)); // when doing equals we need LHS and RHS to be present
+ } else {
+ c.put("when", WhenPresencePredicate.TRUTHY);
+ }
+ conditionConstructed = c;
+ }
+
+ DslPredicate result = getConditionResolved(context, conditionConstructed);
+
+ return result;
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java
index ed7eb0c..6316d45 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/ReturnWorkflowStep.java
@@ -39,7 +39,10 @@
@Override
protected Object doTaskBody(WorkflowStepInstanceExecutionContext context) {
- if (next==null) context.next = STEP_TARGET_NAME_FOR_END;
+ if (next==null) {
+ context.next = STEP_TARGET_NAME_FOR_END;
+ context.nextIsReturn = true;
+ }
return context.getInput(VALUE);
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java
index 416853a..3ce6c33 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/steps/flow/SubWorkflowStep.java
@@ -56,7 +56,7 @@
protected void checkCallerSuppliedDefinition(String typeBestGuess, Map m) {
if (!isInternalClassNotExtendedAndUserAllowedToSetMostThings(typeBestGuess)) {
- throw new IllegalArgumentException("Not permitted to define a custom subworkflow step");
+ throw new IllegalArgumentException("Not permitted to define a custom subworkflow step with this supertype");
}
// these can't be set by user or registered type for subworkflow
FORBIDDEN_IN_SUBWORKFLOW_STEP_ALWAYS.stream().filter(m::containsKey).forEach(forbiddenKey -> {
@@ -75,6 +75,7 @@
@Override
protected Map initializeReducingVariables(WorkflowStepInstanceExecutionContext context, Map<String, Object> reducing) {
+ context.isLocalSubworkflow = true;
return super.initializeReducingVariables(context, context.getWorkflowExectionContext().getWorkflowScratchVariables());
}
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 10124ad..f89f79b 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
@@ -72,6 +72,7 @@
import org.apache.brooklyn.core.workflow.steps.flow.FailWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.ForeachWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.GotoWorkflowStep;
+import org.apache.brooklyn.core.workflow.steps.flow.IfWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.LogWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.NoOpWorkflowStep;
import org.apache.brooklyn.core.workflow.steps.flow.RetryWorkflowStep;
@@ -141,6 +142,7 @@
addRegisteredTypeBean(mgmt, "clear-workflow-variable", ClearVariableWorkflowStep.class);
addRegisteredTypeBean(mgmt, "wait", WaitWorkflowStep.class);
addRegisteredTypeBean(mgmt, "return", ReturnWorkflowStep.class);
+ addRegisteredTypeBean(mgmt, "if", IfWorkflowStep.class);
addRegisteredTypeBean(mgmt, "goto", GotoWorkflowStep.class);
addRegisteredTypeBean(mgmt, "switch", SwitchWorkflowStep.class);
addRegisteredTypeBean(mgmt, "fail", FailWorkflowStep.class);
diff --git a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubAndCustomExtensionEdgeTest.java b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java
similarity index 62%
rename from core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubAndCustomExtensionEdgeTest.java
rename to core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java
index 6431170..4505bd8 100644
--- a/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubAndCustomExtensionEdgeTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/workflow/WorkflowSubIfAndCustomExtensionEdgeTest.java
@@ -20,8 +20,10 @@
import java.io.Serializable;
import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
-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.api.typereg.RegisteredType;
@@ -45,9 +47,9 @@
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
-public class WorkflowSubAndCustomExtensionEdgeTest extends RebindTestFixture<TestApplication> {
+public class WorkflowSubIfAndCustomExtensionEdgeTest extends RebindTestFixture<TestApplication> {
- private static final Logger log = LoggerFactory.getLogger(WorkflowSubAndCustomExtensionEdgeTest.class);
+ private static final Logger log = LoggerFactory.getLogger(WorkflowSubIfAndCustomExtensionEdgeTest.class);
@Override
protected LocalManagementContext decorateOrigOrNewManagementContext(LocalManagementContext mgmt) {
@@ -74,16 +76,16 @@
TestApplication app;
Task<?> lastInvocation;
- Object runWorkflow(List<Object> steps) throws Exception {
+ Object runWorkflow(List<Object> steps) {
return runWorkflow(steps, null);
}
- Object runWorkflow(List<Object> steps, ConfigBag extraEffectorConfig) throws Exception {
+ Object runWorkflow(List<Object> steps, ConfigBag extraEffectorConfig) {
if (app==null) app = mgmt().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
.configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
.configure(WorkflowEffector.STEPS, steps)
.putAll(extraEffectorConfig));
- eff.apply((EntityLocal)app);
+ eff.apply(app);
lastInvocation = app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
return lastInvocation.getUnchecked();
@@ -143,31 +145,76 @@
@Test
public void testSubWorkflowStep() throws Exception {
- MutableList<String> steps = MutableList.of(
- "let v0 = ${v0}B",
- "let v1 = ${v1}B",
- "let v3 = V3B",
- "return done");
- runWorkflow(MutableList.of(
- "let v1 = V1",
- "let v2 = V2",
- MutableMap.of(
- "step", "subworkflow",
- "steps", steps),
- "return ${v0}-${v1}-${v2}-${v3}-${output}"),
- ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0")) );
- Asserts.assertEquals(lastInvocation.getUnchecked(), "V0B-V1B-V2-V3B-done");
+ Function<Boolean,Object> test = (explicitSubworkflow) ->
+ runWorkflow(MutableList.of(
+ "let v1 = V1",
+ "let v2 = V2",
+ MutableMap.<String,Object>of("steps", MutableList.of(
+ "let v0 = ${v0}B",
+ "let v1 = ${v1}B",
+ "let v3 = V3B"))
+ .add(explicitSubworkflow ? MutableMap.of("step", "subworkflow") : null),
+ "return ${v0}-${v1}-${v2}-${v3}"),
+ ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0")) );
+ Asserts.assertEquals(test.apply(true), "V0B-V1B-V2-V3B");
// subworkflow is chosen implicitly if step is omitted
+ Asserts.assertEquals(test.apply(false), "V0B-V1B-V2-V3B");
+
runWorkflow(MutableList.of(
+ MutableMap.of("steps", MutableList.of(
"let v1 = V1",
- "let v2 = V2",
- MutableMap.of(
- //"step", "subworkflow",
- "steps", steps),
- "return ${v0}-${v1}-${v2}-${v3}-${output}"),
- ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0")) );
- Asserts.assertEquals(lastInvocation.getUnchecked(), "V0B-V1B-V2-V3B-done");
+ "goto marker", // prefers inner id 'marker'
+ "let v1 = NOT_V1_1",
+ MutableMap.of("id", "marker", "step", "goto end"), // goes to end of this subworkflow
+ "let v1 = NOT_V1_2")),
+ "let v2 = V2",
+ MutableMap.of("steps", MutableList.of(
+ "let v3 = V3",
+ "goto marker", // goes to outer id 'marker' because nothing local matching
+ "let v3 = NOT_V3")),
+ MutableMap.of("id", "marker", "step", "let v4 = V4"),
+ MutableMap.of("steps", MutableList.of(
+ "return ${v1}-${v2}-${v3}-${v4}")), // returns from outer workflow
+ "let v4 = NOT_V4"));
+ Asserts.assertEquals(lastInvocation.getUnchecked(), "V1-V2-V3-V4");
+ }
+
+ @Test
+ public void testIfWorkflowStep() throws Exception {
+ BiFunction<String,String,Object> run = (preface,cond) ->
+ runWorkflow(MutableList.<Object>of(preface==null ? "let x = hi" : preface,
+ "let y = no",
+ "if "+(cond==null ? "${x}" : cond)+" then let y = yes",
+ "return ${y}"));
+
+ Asserts.assertEquals(run.apply(null, null), "yes");
+
+ Asserts.assertEquals(run.apply("let boolean x = true", null), "yes");
+ Asserts.assertEquals(run.apply("let boolean x = false", null), "no");
+
+ Asserts.assertEquals(run.apply(null, "${x} == hi"), "yes");
+ Asserts.assertEquals(run.apply(null, "${x} == ho"), "no");
+ Asserts.assertEquals(run.apply(null, "hi == ${x}"), "yes");
+ Asserts.assertEquals(run.apply(null, "${x} == ${x}"), "yes");
+ Asserts.assertEquals(run.apply("let boolean x = true", "${x} == true"), "yes");
+
+ // unresolvable things -- allowed iff no == block
+ Asserts.assertEquals(run.apply(null, "${unresolvable_without_equals}"), "no");
+ Asserts.assertFailsWith(() -> run.apply(null, "${x} == ${unresolvable_on_rhs}"),
+ Asserts.expectedFailureContainsIgnoreCase("unresolvable_on_rhs", "missing"));
+ Asserts.assertFailsWith(() -> run.apply(null, "${unresolvable_on_lhs} == ${x}"),
+ Asserts.expectedFailureContainsIgnoreCase("unresolvable_on_lhs", "missing"));
+
+ // flow - returns directly
+ Asserts.assertEquals(runWorkflow(MutableList.of("let boolean x = true",
+ "if ${x} then return yes",
+ "return no")), "yes");
+
+ Asserts.assertEquals(runWorkflow(MutableList.of("let integer x = 1",
+ MutableMap.of("id", "loop", "step", "let x = ${x} + 1"),
+ "if ${x} == 2 then goto loop",
+ "return ${x}")), 3);
}
}
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 4a29156..c6ab621 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
@@ -135,33 +135,33 @@
Asserts.assertEquals(transform("value 'silly world' | replace regex l. k"), "siky world");
Asserts.assertEquals(transform("value 'silly world' | replace all regex l. k"), "siky work");
// with slash
- Asserts.assertEquals(transform("value 'abc/def/ghi' | replace regex 'c/d' XXX"), "abXXXef/ghi");
+ Asserts.assertEquals(transform("value 'abc/def/ghi' | replace regex 'c/d' 000"), "ab000ef/ghi");
// with space
- Asserts.assertEquals(transform("value 'abc def ghi' | replace regex 'c d' XXX"), "abXXXef ghi");
+ Asserts.assertEquals(transform("value 'abc def ghi' | replace regex 'c d' 000"), "ab000ef ghi");
// greedy
- Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace regex 'c.*d' XXX"), "abXXX");
- Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace all regex 'c.*d' XXX"), "abXXX");
+ Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace regex 'c.*d' 000"), "ab000");
+ Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace all regex 'c.*d' 000"), "ab000");
// non-greedy qualifier
- Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace regex 'c.*?d' XXX"), "abXXXef ghi c2d");
- Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace all regex 'c.*?d' XXX"), "abXXXef ghi XXX");
+ Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace regex 'c.*?d' 000"), "ab000ef ghi c2d");
+ Asserts.assertEquals(transform("value 'abc def ghi c2d' | replace all regex 'c.*?d' 000"), "ab000ef ghi 000");
Asserts.assertEquals(transform("value 'abc def ghi' | replace regex 'c d' ''"), "abef ghi");
}
@Test
public void testTransformLiteral() {
- Asserts.assertEquals(transform("value 'abc def ghi' | replace literal c.*d XXX"), "abc def ghi");
- Asserts.assertEquals(transform("value 'abc.*def ghi c.*d' | replace literal c.*d XXX"), "abXXXef ghi c.*d");
- Asserts.assertEquals(transform("value 'abc.*def ghi c.*d' | replace all literal c.*d XXX"), "abXXXef ghi XXX");
+ Asserts.assertEquals(transform("value 'abc def ghi' | replace literal c.*d 000"), "abc def ghi");
+ Asserts.assertEquals(transform("value 'abc.*def ghi c.*d' | replace literal c.*d 000"), "ab000ef ghi c.*d");
+ Asserts.assertEquals(transform("value 'abc.*def ghi c.*d' | replace all literal c.*d 000"), "ab000ef ghi 000");
}
@Test
public void testTransformGlob() {
- Asserts.assertEquals(transform("value 'abc def ghi' | replace glob c*e XXX"), "abXXXf ghi");
+ Asserts.assertEquals(transform("value 'abc def ghi' | replace glob c*e 000"), "ab000f ghi");
// glob is greedy, unless all is specified where it is not
- Asserts.assertEquals(transform("value 'abc def ghi c2e' | replace glob c*e XXX"), "abXXX");
- Asserts.assertEquals(transform("value 'abc def ghi c2e' | replace all glob c*e XXX"), "abXXXf ghi XXX");
+ Asserts.assertEquals(transform("value 'abc def ghi c2e' | replace glob c*e 000"), "ab000");
+ Asserts.assertEquals(transform("value 'abc def ghi c2e' | replace all glob c*e 000"), "ab000f ghi 000");
}
@Test
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
index 26195ff..8a9063f 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/ResourceUtilsTest.java
@@ -149,7 +149,7 @@
@Test(expectedExceptions={NoSuchElementException.class})
public void testClassLoaderDirNotFound() throws Exception {
- String d = utils.getClassLoaderDir("/somewhere/not/found/XXX.xxx");
+ String d = utils.getClassLoaderDir("/somewhere/not/found/ignored.x");
// above should fail
log.warn("Uh oh found imaginary resource in: "+d);
}
diff --git a/karaf/init/src/main/resources/catalog.bom b/karaf/init/src/main/resources/catalog.bom
index c606ff1..30d0836 100644
--- a/karaf/init/src/main/resources/catalog.bom
+++ b/karaf/init/src/main/resources/catalog.bom
@@ -158,6 +158,11 @@
itemType: bean
item:
type: org.apache.brooklyn.core.workflow.steps.flow.ReturnWorkflowStep
+ - id: if
+ format: java-type-name
+ itemType: bean
+ item:
+ type: org.apache.brooklyn.core.workflow.steps.flow.IfWorkflowStep
- id: goto
format: java-type-name
itemType: bean
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynWebServerTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynWebServerTest.java
index b1190db..647a54d 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynWebServerTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynWebServerTest.java
@@ -182,8 +182,8 @@
@Test
public void verifyHttpsCiphers() throws Exception {
brooklynProperties.put(BrooklynWebConfig.HTTPS_REQUIRED, true);
- brooklynProperties.put(BrooklynWebConfig.TRANSPORT_PROTOCOLS, "XXX");
- brooklynProperties.put(BrooklynWebConfig.TRANSPORT_CIPHERS, "XXX");
+ brooklynProperties.put(BrooklynWebConfig.TRANSPORT_PROTOCOLS, "IGNORED");
+ brooklynProperties.put(BrooklynWebConfig.TRANSPORT_CIPHERS, "IGNORED");
try {
verifyHttpsFromConfig(brooklynProperties);
fail("Expected to fail due to unsupported ciphers during connection negotiation");
diff --git a/parent/pom.xml b/parent/pom.xml
index 919f640..c7f53fc 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -116,7 +116,7 @@
<artifactId>maven-jar-plugin</artifactId>
<!-- version 2.4+ seems to break eclipse integration as per https://github.com/tesla/m2eclipse-extras/issues/10
but cannot find issue on GitHub any more - 404 error -->
- <!-- XXX if this version is changed, update the MavenArtifactTest -->
+ <!-- if this version is changed, update the MavenArtifactTest -->
<version>2.6</version>
</plugin>
<plugin>