Skip to content

Commit

Permalink
[patch]: fix single flow graph generation (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
manikmagar authored Sep 26, 2020
1 parent b58cf24 commit 76addf2
Show file tree
Hide file tree
Showing 7 changed files with 880 additions and 16 deletions.
9 changes: 7 additions & 2 deletions src/main/java/com/javastreets/mulefd/DiagramRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.slf4j.LoggerFactory;

import com.javastreets.mulefd.app.CommandModel;
import com.javastreets.mulefd.app.DrawingException;
import com.javastreets.mulefd.drawings.Diagram;
import com.javastreets.mulefd.drawings.DrawingContext;
import com.javastreets.mulefd.model.ComponentItem;
Expand Down Expand Up @@ -43,7 +44,7 @@ Map<String, ComponentItem> prepareKnownComponents() {
log.error(
"Found an invalid configuration line in mule components file. Column count must be 5. Line - {}",
line);
throw new RuntimeException("Invalid mule components configuration file.");
throw new DrawingException("Invalid mule components configuration file.");
}
ComponentItem item = new ComponentItem();
item.setPrefix(part[0]);
Expand All @@ -53,7 +54,7 @@ Map<String, ComponentItem> prepareKnownComponents() {
log.error(
"Wildcard operation entry as a source is not allowed. Please create a separate entry for source if needed. Line - {}",
line);
throw new RuntimeException("Invalid mule components configuration file.");
throw new DrawingException("Invalid mule components configuration file.");
}
item.setPathAttributeName(part[3]);
item.setConfigAttributeName(part[4]);
Expand All @@ -71,6 +72,10 @@ Map<String, ComponentItem> prepareKnownComponents() {
public Boolean render() {
try {
List<FlowContainer> flows = findFlows();
if (commandModel.getFlowName() != null && flows.stream().noneMatch(
flowContainer -> flowContainer.getName().equalsIgnoreCase(commandModel.getFlowName()))) {
throw new DrawingException("Flow not found - " + commandModel.getFlowName());
}
return diagram(flows);
} catch (IOException e) {
log.error("Error while parsing xml file", e);
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/javastreets/mulefd/app/DrawingException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.javastreets.mulefd.app;

public class DrawingException extends RuntimeException {

public DrawingException(String message) {
super(message);
}

public DrawingException(String message, Throwable cause) {
super(message, cause);
}
}
27 changes: 26 additions & 1 deletion src/main/java/com/javastreets/mulefd/drawings/Diagram.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,52 @@ public interface Diagram {

boolean draw(DrawingContext drawingContext);

/**
* This default method returns lines used to write diagram header. Empty line entries are added to
* create space between header and the diagram.
*
* @return String
*/
default String[] getDiagramHeaderLines() {
return new String[] {"Mule Flows - " + name() + " Diagram",
"Generated on : " + getDate() + " by mulefd"};
"Generated on : " + getDate() + " by mulefd", "", ""};
}

boolean supports(DiagramType diagramType);

String name();

/**
* Returns "EEE, dd MMM yyyy hh:mm a" formatted current date.
*
* @return String Date
*/
default String getDate() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy hh:mm a");
LocalDateTime date = LocalDateTime.now();
return date.format(formatter);
}

/**
* This method finds a flow or a sub-flow by a given name.
*
* @param name of a flow/sub-flow to find
* @param components {@link List<Component>} to find flow/sub-flow in.
* @return <{@link FlowContainer}
*/
default FlowContainer targetFlowByName(String name, List<Component> components) {
return components.stream()
.filter(component -> component.isFlowKind() && component.getName().equals(name)).findFirst()
.map(component -> (FlowContainer) component).orElse(null);
}

/**
* This method find flows or sub-flows having given <code>suffix</code> in name.
*
* @param suffix @{@link String}
* @param components {@link List<Component>} to find flow/sub-flow in.
* @return {@link List<FlowContainer>}
*/
default List<FlowContainer> searchFlowBySuffix(String suffix, List<Component> components) {
return components.stream()
.filter(component -> component.isFlowKind() && component.getName().endsWith(suffix))
Expand Down
43 changes: 32 additions & 11 deletions src/main/java/com/javastreets/mulefd/drawings/GraphDiagram.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.javastreets.mulefd.app.DrawingException;
import com.javastreets.mulefd.model.Component;
import com.javastreets.mulefd.model.FlowContainer;
import com.javastreets.mulefd.model.MuleComponent;
Expand All @@ -41,16 +42,32 @@ public boolean draw(DrawingContext drawingContext) {
List<Component> flows = drawingContext.getComponents();
Path singleFlowDirPath = Paths.get(targetDirectory.getAbsolutePath(), "single-flow-diagrams",
DateUtil.now("ddMMyyyy-HHmmss"));
if (drawingContext.getFlowName() != null) {
Component component = flows.stream()
.filter(component1 -> component1.getName().equalsIgnoreCase(drawingContext.getFlowName()))
.findFirst().orElseThrow(() -> new DrawingException(
"Target flow not found - " + drawingContext.getFlowName()));
MutableNode flowNode = processComponent(component, drawingContext, flowRefs, mappedFlowKinds);
flowNode.addTo(rootGraph);
}

for (Component component : flows) {
if (drawingContext.getFlowName() == null
|| component.getName().equalsIgnoreCase(drawingContext.getFlowName())) {
MutableGraph flowGraph = initNewGraph();
|| mappedFlowKinds.contains(component.qualifiedName())) {

MutableNode flowNode =
processComponent(component, drawingContext, flowRefs, mappedFlowKinds);

flowNode.addTo(flowGraph);

if (drawingContext.isGenerateSingles() && component.isaFlow()) {
MutableGraph flowGraph = initNewGraph();
flowNode.addTo(flowGraph);
for (Component component2 : flows) {
if (mappedFlowKinds.contains(component2.qualifiedName())) {
MutableNode flowNode3 =
processComponent(component2, drawingContext, flowRefs, mappedFlowKinds);
flowNode3.addTo(flowGraph);
}
}
writeFlowGraph(component, singleFlowDirPath, flowGraph);
}
flowNode.addTo(rootGraph);
Expand Down Expand Up @@ -111,6 +128,7 @@ private void checkUnusedNodes(MutableGraph graph) {

MutableNode processComponent(Component component, DrawingContext drawingContext,
Map<String, Component> flowRefs, List<String> mappedFlowKinds) {
log.debug("Processing flow - {}", component.qualifiedName());
FlowContainer flow = (FlowContainer) component;
Consumer<MutableNode> asFlow = flowNode -> flowNode.add(Shape.RECTANGLE).add(Color.BLUE);
MutableNode flowNode = mutNode(flow.qualifiedName()).add(Label.markdown(getNodeLabel(flow)));
Expand All @@ -121,8 +139,8 @@ MutableNode processComponent(Component component, DrawingContext drawingContext,
}
MutableNode sourceNode = null;
boolean hasSource = false;
for (int j = 1; j <= flow.getComponents().size(); j++) {
MuleComponent muleComponent = flow.getComponents().get(j - 1);
for (int componentIdx = 1; componentIdx <= flow.getComponents().size(); componentIdx++) {
MuleComponent muleComponent = flow.getComponents().get(componentIdx - 1);
// Link style should be done with .linkTo()
String name = muleComponent.qualifiedName();
if (muleComponent.isaFlowRef()) {
Expand Down Expand Up @@ -154,6 +172,7 @@ MutableNode processComponent(Component component, DrawingContext drawingContext,
// 1. Create a new apikit node for this component
// 2. Find all flows with name ending with ":{apikiConfigName}"
// 3. Link those flows with apiKit flow.
log.debug("Processing apikit component - {}", component.qualifiedName());
MutableNode apiKitNode =
mutNode(muleComponent.getType().concat(muleComponent.getConfigRef().getValue()))
.add(Label.htmlLines("<b>" + muleComponent.getType() + "</b>",
Expand All @@ -166,9 +185,10 @@ MutableNode processComponent(Component component, DrawingContext drawingContext,
asFlow.accept(node);
apiKitNode.addLink(to(node).with(Style.SOLID));
}
flowNode.addLink(to(apiKitNode).with(Style.SOLID, Label.of("(" + (j - 1) + ")")));
flowNode
.addLink(to(apiKitNode).with(Style.SOLID, Label.of("(" + (componentIdx - 1) + ")")));
} else {
addSubNodes(flowNode, hasSource ? j - 1 : j, muleComponent, name);
addSubNodes(flowNode, hasSource ? componentIdx - 1 : componentIdx, muleComponent, name);
}

mappedFlowKinds.add(name);
Expand All @@ -183,12 +203,13 @@ private String getNodeLabel(Component component) {
return String.format("**%s**: %s", component.getType(), component.getName());
}

private void addSubNodes(MutableNode flowNode, int j, MuleComponent muleComponent, String name) {
private void addSubNodes(MutableNode flowNode, int callSequence, MuleComponent muleComponent,
String name) {
if (muleComponent.isAsync()) {
flowNode.addLink(to(mutNode(name)).with(Style.DASHED.and(Style.BOLD),
Label.of("(" + j + ") Async"), Color.BROWN));
Label.of("(" + callSequence + ") Async"), Color.BROWN));
} else {
flowNode.addLink(to(mutNode(name)).with(Style.SOLID, Label.of("(" + j + ")")));
flowNode.addLink(to(mutNode(name)).with(Style.SOLID, Label.of("(" + callSequence + ")")));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static guru.nidi.graphviz.attribute.Arrow.VEE;
import static guru.nidi.graphviz.model.Factory.mutGraph;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -168,6 +169,35 @@ void drawToValidateGraph_APIKIT() throws Exception {

}

@Test
@DisplayName("Validate generated graph for Single flow when generated as JSON.")
void drawToValidateGraph_SingleFlow() throws Exception {

List flows = DiagramRendererTestUtil
.getFlows(Paths.get("src/test/resources/single-flow-generation-example.xml"));
File output = new File(".", "output.png");
DrawingContext context = new DrawingContext();
context.setDiagramType(DiagramType.GRAPH);
context.setOutputFile(output);
context.setComponents(flows);
context.setFlowName("sub-flow-level-1-2");
context.setKnownComponents(Collections.emptyMap());

GraphDiagram graphDiagram = Mockito.spy(new GraphDiagram());
when(graphDiagram.getDiagramHeaderLines()).thenReturn(new String[] {"Test Diagram"});
graphDiagram.draw(context);
ArgumentCaptor<MutableGraph> graphArgumentCaptor = ArgumentCaptor.forClass(MutableGraph.class);
verify(graphDiagram).writGraphToFile(any(File.class), graphArgumentCaptor.capture());
MutableGraph generatedGraph = graphArgumentCaptor.getValue();
Graphviz.useEngine(new GraphvizV8Engine());
String jsonGraph = Graphviz.fromGraph(generatedGraph).render(Format.JSON).toString();
System.out.println(jsonGraph);
String ref = new String(
Files.readAllBytes(Paths.get("src/test/resources/single-flow-generation-example.json")));
JSONAssert.assertEquals(ref, jsonGraph, JSONCompareMode.STRICT);
Graphviz.releaseEngine();

}

@Test
void drawWithSinglesGeneration() {
Expand Down Expand Up @@ -235,13 +265,36 @@ void drawASingleFlow() {
System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
assertThat(output).exists();
ArgumentCaptor<Component> compArg = ArgumentCaptor.forClass(Component.class);
verify(graphDiagram, Mockito.times(2)).processComponent(compArg.capture(), eq(context),
verify(graphDiagram, Mockito.times(3)).processComponent(compArg.capture(), eq(context),
anyMap(), anyList());
assertThat(compArg.getAllValues()).containsExactly(flowContainer2, subflow);
assertThat(compArg.getAllValues()).containsExactly(flowContainer2, subflow, subflow);
logs.assertContains(
"Detected a possible self loop in sub-flow test-sub-flow. Skipping flow-ref processing.");
}


@Test
@DisplayName("Throw error when given flow name not found")
void drawASingleFlow_WhenFlowNotFound() {
// Get the Java runtime
Runtime runtime = Runtime.getRuntime();
// Run the garbage collector
runtime.gc();
File output = new File(tempDir, "output.png");
DrawingContext context = new DrawingContext();
context.setDiagramType(DiagramType.GRAPH);
context.setOutputFile(output);
context.setFlowName("wrong-flow-name");
FlowContainer flowContainer = new FlowContainer("flow", "test-flow-1");

context.setComponents(Arrays.asList(flowContainer));
context.setKnownComponents(Collections.emptyMap());

GraphDiagram graphDiagram = Mockito.spy(new GraphDiagram());
Throwable throwable = catchThrowable(() -> graphDiagram.draw(context));
assertThat(throwable).isNotNull().hasMessage("Target flow not found - wrong-flow-name");
}

@Test
void supports() {
GraphDiagram graphDiagram = new GraphDiagram();
Expand Down
Loading

0 comments on commit 76addf2

Please sign in to comment.