in core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowExpressionResolution.java [145:251]
public TemplateModel get(String key) throws TemplateModelException {
List<Throwable> errors = MutableList.of();
if ("workflow".equals(key)) {
return new WorkflowExplicitModel();
}
if ("entity".equals(key)) {
Entity entity = context.getEntity();
if (entity!=null) {
return TemplateProcessor.EntityAndMapTemplateModel.forEntity(entity, null);
}
}
if ("output".equals(key)) {
if (context.getOutput()!=null) return TemplateProcessor.wrapAsTemplateModel(context.getOutput());
if (context.currentStepInstance!=null && context.currentStepInstance.getOutput() !=null) return TemplateProcessor.wrapAsTemplateModel(context.currentStepInstance.getOutput());
Object previousStepOutput = context.getPreviousStepOutput();
if (previousStepOutput!=null) return TemplateProcessor.wrapAsTemplateModel(previousStepOutput);
return ifNoMatches();
}
Object candidate = null;
if (stage.after(WorkflowExpressionStage.STEP_PRE_INPUT)) {
//somevar -> workflow.current_step.output.somevar
WorkflowStepInstanceExecutionContext currentStep = context.currentStepInstance;
if (currentStep != null && stage.after(WorkflowExpressionStage.STEP_OUTPUT)) {
if (currentStep.getOutput() instanceof Map) {
candidate = ((Map) currentStep.getOutput()).get(key);
if (candidate != null) return TemplateProcessor.wrapAsTemplateModel(candidate);
}
}
//somevar -> workflow.current_step.input.somevar
try {
if (currentStep!=null) {
candidate = currentStep.getInput(key, Object.class);
}
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
if (stage==WorkflowExpressionStage.STEP_INPUT && WorkflowVariableResolutionStackEntry.isStackForSettingVariable(RESOLVE_STACK.getAll(true), key) && Exceptions.getFirstThrowableOfType(t, WorkflowVariableRecursiveReference.class)!=null) {
// input evaluation can look at local input, and will gracefully handle some recursive references.
// this is needed so we can handle things like env:=${env} in input, and also {message:="Hi ${name}", name:="Bob"}.
// but there are
// if we have a chain input1:=input2, and input input2:=input1 with both defined on step and on workflow
//
// (a) eval of either will give recursive reference error and allow retry immediately;
// then it's a bit weird, inconsistent, step input1 will resolve to local input2 which resolves as global input1;
// but step input2 will resolve to local input1 which this time will resolve as global input2.
// and whichever is invoked first will cause both to be stored as resolved, so if input2 resolved first then
// step input1 subsequently returns global input2.
//
// (b) recursive reference error only recoverable at the outermost stage,
// so step input1 = global input2, step input2 = global input1,
// prevents inconsistency but blocks useful things, eg log ${message} wrapped with message:="Hi ${name}",
// then invoked with name: "person who says ${message}" to refer to a previous step's message,
// or even name:="Mr ${name}" to refer to an outer variable.
// in this case if name is resolved first then message resolves as Hi Mr X, but if message resolved first
// it only recovers when resolving message which would become "Hi X", and if message:="${greeting} ${name}"
// then it fails to find a local ${greeting}. (with strategy (a) these both do what is expected.)
//
// (to handle this we include stage in the stack, needed in both cases above)
//
// ideally we would know which vars are from a wrapper, but that info is lost when we build up the step
//
// (c) we could just fail fast, disallow the nice things we wanted, require explicit
//
// (d) we could fail in edge cases, so the obvious cases above work as expected, but anything more sophisticated, eg A calling B calling A, will fail
//
// settled on (d) effectively; we allow local references, and fail on recursive references, with exceptions.
// the main exception, handled here, is if we are setting an input
candidate = null;
errors.add(t);
} else {
throw Exceptions.propagate(t);
}
}
if (candidate != null) return TemplateProcessor.wrapAsTemplateModel(candidate);
}
//workflow.previous_step.output.somevar
if (stage.after(WorkflowExpressionStage.WORKFLOW_INPUT)) {
Object prevStepOutput = context.getPreviousStepOutput();
if (prevStepOutput instanceof Map) {
candidate = ((Map) prevStepOutput).get(key);
if (candidate != null) return TemplateProcessor.wrapAsTemplateModel(candidate);
}
}
//workflow.scratch.somevar
if (stage.after(WorkflowExpressionStage.WORKFLOW_INPUT)) {
candidate = context.getWorkflowScratchVariables().get(key);
if (candidate != null) return TemplateProcessor.wrapAsTemplateModel(candidate);
}
//workflow.input.somevar
if (context.input.containsKey(key)) {
candidate = context.getInput(key);
// the subtlety around step input above doesn't apply here as workflow inputs are not resolved with freemarker
if (candidate != null) return TemplateProcessor.wrapAsTemplateModel(candidate);
}
if (!errors.isEmpty()) Exceptions.propagate("Errors resolving "+key, errors);
return ifNoMatches();
}