package io.cattle.platform.extension.api.dot; import io.cattle.platform.engine.process.ExtensionBasedProcessDefinition; import io.cattle.platform.engine.process.ProcessDefinition; import io.cattle.platform.engine.process.StateTransition; import io.cattle.platform.engine.process.StateTransition.Style; import io.cattle.platform.engine.process.impl.ResourceStatesDefinition; import io.cattle.platform.extension.ExtensionImplementation; import io.cattle.platform.extension.ExtensionPoint; import io.cattle.platform.extension.api.model.ProcessDefinitionApi; import io.github.ibuildthecloud.gdapi.context.ApiContext; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; public class DotMaker { List<ProcessDefinition> processDefinitions; String html; public String getResourceDot(String resourceType) { StringBuilder buffer = new StringBuilder(); Set<String> nodes = new HashSet<String>(); buffer.append("digraph \"").append(resourceType).append("\" {\n"); for (ProcessDefinition def : processDefinitions) { if (ObjectUtils.equals(resourceType, def.getResourceType())) { addTransitions(def, nodes, buffer); } } buffer.append("}\n"); return buffer.toString(); } public boolean writeResponse(String dot, ApiRequest request) throws IOException { if (dot != null) { String content = getContent(request, dot); try { request.getOutputStream().write(content.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } return true; } return false; } protected String getContent(ApiRequest request, String dot) { if ("html".equals(request.getResponseFormat())) { request.setResponseContentType("text/html; charset=utf-8"); return asHtml(dot); } else { request.setResponseContentType("text/plain"); return dot; } } protected String asHtml(String dot) { return html.replace("%DOT%", dot); } protected void addTransitions(ProcessDefinition def, Set<String> nodes, StringBuilder buffer) { String doneName = getName(def); String name = def.getName(); String link = getLink(def); List<StateTransition> transitions = def.getStateTransitions(); for (StateTransition transition : transitions) { String from = getTransitionName(transition, transition.getFromState()); String to = getTransitionName(transition, transition.getToState()); buffer.append(" \"").append(from).append("\" -> \"").append(to).append("\""); if (transition.getType() == Style.TRANSITIONING) { buffer.append(" [style=\"dotted\" label=\"" + name + "\" URL=\"" + link + "\"]"); } if (transition.getType() == Style.DONE) { buffer.append(" [color=\"orange\" label=\"" + doneName + "\" URL=\"" + link + "\"]"); } buffer.append(";\n"); if (transition.getType() == Style.TRANSITIONING && !nodes.contains(to)) { buffer.append(" \"").append(to).append("\" [color=\"orange\"];\n"); nodes.add(to); } } } protected String getTransitionName(StateTransition transition, String name) { if (ResourceStatesDefinition.DEFAULT_STATE_FIELD.equals(transition.getField())) { return name; } else { return transition.getField() + ":" + name; } } public String getProcessDot(ProcessDefinition def) { StringBuilder buffer = new StringBuilder(); Set<String> nodes = new HashSet<String>(); buffer.append("digraph \"").append(def.getName()).append("\" {\n"); addTransitions(def, nodes, buffer); buffer.append("}\n"); return buffer.toString(); } protected String getName(ProcessDefinition def) { StringBuilder buffer = new StringBuilder(def.getName()); buffer.append("["); if (def instanceof ExtensionBasedProcessDefinition) { int size = buffer.length(); appendExtensionPoint(size, buffer, "pre", ((ExtensionBasedProcessDefinition) def).getPreProcessListenersExtensionPoint()); appendExtensionPoint(size, buffer, "logic", ((ExtensionBasedProcessDefinition) def).getProcessHandlersExtensionPoint()); appendExtensionPoint(size, buffer, "post", ((ExtensionBasedProcessDefinition) def).getPostProcessListenersExtensionPoint()); if (buffer.length() == size) { buffer.append("no-op"); } buffer.append("] "); } String result = buffer.toString(); if (!result.contains("no-op")) { result += "\" fontname=\"times-bold\" color=\"green\" style=\"bold"; } return result; } protected String getLink(ProcessDefinition def) { return ApiContext.getUrlBuilder().resourceReferenceLink(ProcessDefinitionApi.class, def.getName()).toExternalForm(); } protected void appendExtensionPoint(int size, StringBuilder buffer, String title, ExtensionPoint point) { if (point.getImplementations().size() == 0) { return; } if (buffer.length() > size) { buffer.append(","); } buffer.append(title).append("="); int count = 0; for (ExtensionImplementation impl : point.getImplementations()) { if (count > 0) { buffer.append(","); } buffer.append(impl.getName()); count++; } } @PostConstruct public void init() throws IOException { InputStream is = null; try { is = getClass().getResourceAsStream("html-override.txt"); if (is == null) { is = getClass().getResourceAsStream("html.txt"); } if (is != null) { this.html = IOUtils.toString(is); } } finally { IOUtils.closeQuietly(is); } } public List<ProcessDefinition> getProcessDefinitions() { return processDefinitions; } @Inject public void setProcessDefinitions(List<ProcessDefinition> processDefinitions) { this.processDefinitions = processDefinitions; } }