package alien4cloud.paas.wf.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.elasticsearch.common.collect.Lists;
import alien4cloud.paas.wf.AbstractStep;
import alien4cloud.paas.wf.Path;
import alien4cloud.paas.wf.Workflow;
import alien4cloud.paas.wf.exception.InconsistentWorkflowException;
public class WorkflowGraphUtils {
public static AbstractStep getRequiredStep(Workflow workflow, String stepId) {
if (workflow.getSteps() == null) {
throw new InconsistentWorkflowException("The workflow doesn't contain any steps");
}
AbstractStep step = workflow.getSteps().get(stepId);
if (step == null) {
throw new InconsistentWorkflowException(String.format("The workflow doesn't contains the expected step <%s> !", stepId));
}
return step;
}
/**
* Build the paths of the graph starting from the entry points (steps without predecessors, so connected to 'start').
* <p>
* Will also detect orphans brothers in the entire graph (cycles not connected to start).
*/
public static List<Path> getWorkflowGraphPaths(Workflow workflow) {
// the result
List<Path> allPaths = new ArrayList<Path>();
// find the entry steps
Set<AbstractStep> graphEntries = getGraphEntrySteps(workflow);
// all the steps: this set will be cleared while browsing the graph and will able us to detect orphans
Set<AbstractStep> allSteps = new HashSet<AbstractStep>(workflow.getSteps().values());
for (AbstractStep graphEntry : graphEntries) {
List<Path> initialPaths = new ArrayList<Path>();
// we have 1 initial path : it's the 'start'
Path startPath = new Path();
initialPaths.add(startPath);
recursivelyPopulatePaths(workflow, allPaths, initialPaths, graphEntry, allSteps);
}
while (!allSteps.isEmpty()) {
// we have orphans steps that are not on any path, in cycles
// so just peek the first one, and build paths until this set is empty
AbstractStep step = allSteps.iterator().next();
List<Path> initialPaths = new ArrayList<Path>();
// initial path
Path startPath = new Path();
initialPaths.add(startPath);
recursivelyPopulatePaths(workflow, allPaths, initialPaths, step, allSteps);
}
return allPaths;
}
/**
* TODO: is this a tail recursive fn ?
*/
private static void recursivelyPopulatePaths(Workflow workflow, List<Path> allPaths, List<Path> predecessors, AbstractStep step, Set<AbstractStep> allSteps) {
boolean stepHasFollowers = step.getFollowingSteps() != null && !step.getFollowingSteps().isEmpty();
for (Path path : predecessors) {
if (path.contains(step)) {
// the step is already in one of it's parent path, we have a cycle
path.setCycle(true);
path.setLoopingStep(step);
} else {
// a step is added to it's predecessor paths
path.add(step);
// since the step is on a path, we remove it from the allSteps set
allSteps.remove(step);
}
if (path.isCycle() || !stepHasFollowers) {
// this is the end for this path
allPaths.add(path);
}
}
if (stepHasFollowers) {
for (String followingId : step.getFollowingSteps()) {
AbstractStep followingStep = WorkflowGraphUtils.getRequiredStep(workflow, followingId);
List<Path> followersPredecessors = Lists.newArrayList();
for (Path path : predecessors) {
if (!path.isCycle()) {
Path clone = new Path(path);
followersPredecessors.add(clone);
}
}
if (followersPredecessors.size() > 0) {
recursivelyPopulatePaths(workflow, allPaths, followersPredecessors, followingStep, allSteps);
}
}
}
}
/**
* The graph entry is the lists of steps that haven't predecessor: these steps are de-facto connected to 'start'.
*/
public static Set<AbstractStep> getGraphEntrySteps(Workflow workflow) {
Set<AbstractStep> entries = new HashSet<AbstractStep>();
for (AbstractStep step : workflow.getSteps().values()) {
if (step.getPrecedingSteps() == null || step.getPrecedingSteps().isEmpty()) {
entries.add(step);
}
}
return entries;
}
}