package ee.telekom.workflow.graph.core; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import ee.telekom.workflow.graph.Graph; import ee.telekom.workflow.graph.GraphValidator; import ee.telekom.workflow.graph.Node; import ee.telekom.workflow.graph.Transition; import ee.telekom.workflow.graph.el.ElUtil; import ee.telekom.workflow.graph.node.activity.BeanAsyncCallActivity; import ee.telekom.workflow.graph.node.activity.BeanCallActivity; import ee.telekom.workflow.graph.node.activity.CreateNewInstanceActivity; import ee.telekom.workflow.graph.node.activity.HumanTaskActivity; import ee.telekom.workflow.graph.node.activity.ObjectCallActivity; import ee.telekom.workflow.graph.node.activity.SetAttributeActivity; import ee.telekom.workflow.graph.node.event.CatchSignal; import ee.telekom.workflow.graph.node.input.MapMapping; import ee.telekom.workflow.graph.node.output.MapEntryMapping; import ee.telekom.workflow.graph.node.output.OutputMapping; import ee.telekom.workflow.graph.node.output.ValueMapping; public class GraphValidatorImpl implements GraphValidator{ @Override public List<String> validate( Graph graph ){ List<String> errors = new LinkedList<>(); // validate name and version validateNameAndVersion( graph, errors ); if( !errors.isEmpty() ){ return errors; } // allow empty graphs if( graph.getNodes().isEmpty() && graph.getTransitions().isEmpty() ){ return errors; } // otherwise check for mandatory start node validateStartNode( graph, errors ); if( !errors.isEmpty() ){ return errors; } // and for any unreachable nodes validateNoUnreachableNodes( graph, errors ); // check for reserved variable names validateReservedVariableNames( graph, errors ); return errors; } private static void validateNameAndVersion( Graph graph, List<String> errors ){ if( StringUtils.isBlank( graph.getName() ) ){ errors.add( "Graph does not define a name" ); } if( graph.getVersion() <= 0 ){ errors.add( "Graph version is not a positive number." ); } } private static void validateStartNode( Graph graph, List<String> errors ){ if( graph.getStartNode() == null ){ errors.add( describe( graph ) + " does not define a start node" ); } } private static void validateNoUnreachableNodes( Graph graph, List<String> errors ){ Set<Integer> reachables = new TreeSet<>(); Queue<Node> queue = new LinkedList<>(); queue.offer( graph.getStartNode() ); Node reachable; while( (reachable = queue.poll()) != null ){ if( reachables.contains( reachable.getId() ) ){ continue; } reachables.add( reachable.getId() ); for( Transition transition : graph.getOutputTransitions( reachable ) ){ queue.add( transition.getEndNode() ); } } for( Node node : graph.getNodes() ){ if( !reachables.contains( node.getId() ) ){ errors.add( describe( graph ) + " contains unreachable node " + describe( node ) ); } } } private static String describe( Graph graph ){ return graph.getName() + ":" + graph.getVersion(); } private static String describe( Node node ){ return node.getId() + " " + node.getName() + " (" + node.getClass().getSimpleName() + ")"; } /** * check through all the node types which use mappings which set Environment attributes */ private static void validateReservedVariableNames( Graph graph, List<String> errors ){ for( Node node : graph.getNodes() ){ if( node instanceof BeanAsyncCallActivity ){ validateReservedVariablesFromMapping( ((BeanAsyncCallActivity)node).getResultMapping(), graph, node, errors ); } else if( node instanceof BeanCallActivity ){ validateReservedVariablesFromMapping( ((BeanCallActivity)node).getResultMapping(), graph, node, errors ); } else if( node instanceof CreateNewInstanceActivity ){ MapMapping mapMapping = ((CreateNewInstanceActivity)node).getArgumentsMapping(); if( mapMapping != null && mapMapping.getEntryMappings() != null ){ for( String key : mapMapping.getEntryMappings().keySet() ){ validateReservedVariable( key, graph, node, errors ); } } } else if( node instanceof HumanTaskActivity ){ validateReservedVariablesFromMapping( ((HumanTaskActivity)node).getResultMapping(), graph, node, errors ); } else if( node instanceof ObjectCallActivity ){ validateReservedVariablesFromMapping( ((ObjectCallActivity)node).getResultMapping(), graph, node, errors ); } else if( node instanceof SetAttributeActivity ){ validateReservedVariable( ((SetAttributeActivity)node).getAttribute(), graph, node, errors ); } else if( node instanceof CatchSignal ){ validateReservedVariablesFromMapping( ((CatchSignal)node).getResultMapping(), graph, node, errors ); } } } private static void validateReservedVariablesFromMapping( OutputMapping outputMapping, Graph graph, Node node, List<String> errors ){ if( outputMapping instanceof MapEntryMapping ){ Map<String, String> mappings = ((MapEntryMapping)outputMapping).getMappings(); if( mappings != null ){ for( String variableName : mappings.values() ){ validateReservedVariable( variableName, graph, node, errors ); } } } else if( outputMapping instanceof ValueMapping ){ validateReservedVariable( ((ValueMapping)outputMapping).getName(), graph, node, errors ); } } private static void validateReservedVariable( String variableName, Graph graph, Node node, List<String> errors ){ if( ElUtil.isReservedVariable( variableName ) ){ errors.add( describe( graph ) + " node " + describe( node ) + " contains a reserved variable name: " + variableName ); } } }