blob: 358687a0480a75ae4b15faf6580f8a48474d46e6 [file] [log] [blame]
/*
* 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 java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityAdjuncts.EntityAdjunctProxyable;
import org.apache.brooklyn.core.mgmt.internal.AbstractManagementContext;
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;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
public class WorkflowStepResolution {
public static List<WorkflowStepDefinition> resolveSteps(ManagementContext mgmt, List<Object> steps) {
return resolveSteps(mgmt, steps, null);
}
public static List<WorkflowStepDefinition> resolveSteps(ManagementContext mgmt, List<Object> steps, Object outputDefinition) {
return new WorkflowStepResolution(mgmt, null, null).resolveSteps(steps, outputDefinition);
}
public static List<WorkflowStepDefinition> resolveSubSteps(ManagementContext mgmt, String scope, List<Object> subSteps) {
return new WorkflowStepResolution(mgmt, null, null).resolveSubSteps(scope, subSteps);
}
private final ManagementContext _mgmt;
private final BrooklynObject _broolynObject;
private final WorkflowExecutionContext _workflow;
public WorkflowStepResolution(WorkflowExecutionContext context) {
this(null, null, context);
}
public WorkflowStepResolution(BrooklynObject bo) {
this(null, bo, null);
}
public WorkflowStepResolution(ManagementContext mgmt, BrooklynObject bo, WorkflowExecutionContext workflow) {
this._mgmt = mgmt;
this._broolynObject = bo;
this._workflow = workflow;
}
public ManagementContext mgmt() {
if (_mgmt!=null) return _mgmt;
if (_workflow!=null) return _workflow.getManagementContext();
BrooklynObject bo = brooklynObject();
if (bo instanceof BrooklynObjectInternal) return ((BrooklynObjectInternal)bo).getManagementContext();
if (bo instanceof EntityAdjunctProxyable) return ((EntityAdjunctProxyable)bo).getManagementContext();
return null;
}
public BrooklynObject brooklynObject() {
if (_broolynObject!=null) return _broolynObject;
if (_workflow!=null) return _workflow.getEntityOrAdjunctWhereRunning();
return null;
}
public Entity entity() {
BrooklynObject bo = brooklynObject();
if (bo==null || (bo instanceof Entity)) return (Entity) bo;
if (bo instanceof EntityAdjunctProxyable) return ((EntityAdjunctProxyable)bo).getEntity();
return null;
}
public List<WorkflowStepDefinition> resolveSteps(List<Object> steps, Object outputDefinition) {
List<WorkflowStepDefinition> result = MutableList.of();
if (steps==null || steps.isEmpty()) {
if (outputDefinition==null) throw new IllegalStateException("No steps defined in workflow and no output set");
// if there is output, an empty workflow makes sense
return result;
}
for (int i=0; i<steps.size(); i++) {
try {
result.add(resolveStep(steps.get(i)));
} catch (Exception e) {
throw Exceptions.propagateAnnotated("Error in definition of step "+(i+1)+" ("+steps.get(i)+")", e);
}
}
WorkflowExecutionContext.validateSteps(mgmt(), result, true);
return result;
}
public List<WorkflowStepDefinition> resolveSubSteps(String scope, List<Object> subSteps) {
List<WorkflowStepDefinition> result = MutableList.of();
if (subSteps!=null) {
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);
// 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);
});
}
return result;
}
public WorkflowStepDefinition resolveStep(Object def) {
if (def instanceof WorkflowStepDefinition) return (WorkflowStepDefinition) def;
BrooklynClassLoadingContext loader = RegisteredTypes.getClassLoadingContextMaybe(brooklynObject()).or(() -> RegisteredTypes.getCurrentClassLoadingContextOrManagement(mgmt()));
String shorthand = null;
Map defM = null;
if (def instanceof List) {
// list treated as implicit subworkflow, eg step: [ "sleep 1" ] = step: { steps: [ "sleep 1" ] }
def = MutableMap.of(SubWorkflowStep.SHORTHAND_TYPE_NAME_DEFAULT, def);
}
if (def instanceof String) {
shorthand = (String) def;
defM = MutableMap.of();
} else if (def instanceof Map) {
defM = MutableMap.copyOf((Map)def);
if (!defM.containsKey("type")) {
// if there isn't a type, pull out shorthand
Object s = defM.remove("step");
if (s == null) s = defM.remove("shorthand");
if (s == null) s = defM.remove("s");
if (s==null && defM.containsKey(WorkflowCommonConfig.STEPS.getName())) {
// if it has steps, but no step or s, assume it is a subworkflow
s = SubWorkflowStep.SHORTHAND_TYPE_NAME_DEFAULT;
}
if (s == null && defM.size()==1) {
// assume the colon caused it accidentally to be a map
s = Iterables.getOnlyElement(defM.keySet());
if (s instanceof String && ((String)s).contains(" ")) {
s = s + " : " + Iterables.getOnlyElement(defM.values());
} else {
s = null;
}
}
if (s==null) {
throw new IllegalArgumentException("Step definition must indicate a `type` or a `step` / `shorthand` / `s` (" + def + ")");
}
if (s instanceof Map && defM.size()==1) {
// allow shorthand to contain a nested map if the shorthand is the only thing in the map, eg { step: { step: "xxx" } }
return resolveStep(s);
}
if (!(s instanceof String)) {
throw new IllegalArgumentException("step shorthand must be a string");
}
shorthand = (String) s;
}
}
String userSuppliedShorthand = shorthand;
if (shorthand!=null) {
shorthand = shorthand.trim();
int wordBreak = shorthand.indexOf(" ");
if (defM.containsKey("type")) throw new IllegalStateException("Must not supply 'type' when shorthand is used for step");
if (wordBreak<0) {
defM.put("type", shorthand);
shorthand = null;
} else {
defM.put("type", shorthand.substring(0, wordBreak));
shorthand = shorthand.substring(wordBreak + 1).trim();
}
}
String typeBestGuess = defM != null ? ""+defM.get("type") : null;
try {
Object def0 = defM !=null ? defM : def;
// if it's unable to convert a complex type via the above, the original type will be returned; the above doesn't fail.
// this is checked below so it's not a serious error, but the reason for it might be obscured.
Callable<Object> converter = () -> BeanWithTypeUtils.convert(mgmt(), def0, TypeToken.of(WorkflowStepDefinition.class), true, loader, true);
Entity entity = entity();
if (entity==null) {
def = converter.call();
} else {
// run in a task context if we can, to facilitate conversion and type lookup
def = Entities.submit(entity, Tasks.create("convert steps", converter)).getUnchecked();
}
if (def instanceof WorkflowStepDefinition.WorkflowStepDefinitionWithSpecialDeserialization) {
def = ((WorkflowStepDefinition.WorkflowStepDefinitionWithSpecialDeserialization)def).applySpecialDefinition(mgmt(), def0, typeBestGuess, (WorkflowStepDefinition.WorkflowStepDefinitionWithSpecialDeserialization) def);
}
} catch (Exception e) {
throw Exceptions.propagateAnnotated("Unable to resolve step '"+def+"'", e);
}
if (def instanceof WorkflowStepDefinition) {
WorkflowStepDefinition defW = (WorkflowStepDefinition) def;
if (userSuppliedShorthand!=null) {
defW.userSuppliedShorthand = userSuppliedShorthand;
}
if (typeBestGuess!=null) {
defW.shorthandTypeName = typeBestGuess;
}
if (shorthand!=null) {
defW.populateFromShorthand(shorthand);
}
List<Object> onError = WorkflowErrorHandling.wrappedInListIfNecessaryOrNullIfEmpty(defW.getOnError());
if (onError!=null && !onError.isEmpty()) {
defW.onError = resolveSubSteps("error handling", onError);
}
defW.validateStep(mgmt(), null);
return defW;
} else {
throw new IllegalArgumentException("Unable to resolve step; unexpected object "+ def);
}
}
public static void validateWorkflowParametersForEffector(BrooklynObject entityOrAdjunctWhereRunningIfKnown, ConfigBag params) {
List<Object> steps = params.get(WorkflowCommonConfig.STEPS);
if ((steps==null || steps.isEmpty()) && !params.containsKey(WorkflowCommonConfig.OUTPUT))
throw new IllegalArgumentException("It is required to supply 'steps' or 'output' to define a workflow effector");
boolean hasCondition = params.containsKey(WorkflowCommonConfig.CONDITION.getName());
if (!hasCondition && entityOrAdjunctWhereRunningIfKnown!=null) {
// ideally try to resolve the steps at entity init time; except if a condition is required we skip that so you can have steps that only resolve late,
// and if entity isn't available then we don't need that either
new WorkflowStepResolution(entityOrAdjunctWhereRunningIfKnown).resolveSteps(steps, params.containsKey(WorkflowCommonConfig.OUTPUT.getName()) ? "has_output" : null);
}
}
public static Maybe<Entity> findEntity(WorkflowStepInstanceExecutionContext context, Object entityO) {
return AbstractManagementContext.findEntity(context.getEntity(), entityO);
}
}