001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.view;
018
019 import java.io.PrintWriter;
020 import java.util.List;
021 import java.util.Map;
022 import java.util.Set;
023
024 import org.apache.camel.model.FromDefinition;
025 import org.apache.camel.model.InterceptorDefinition;
026 import org.apache.camel.model.MulticastDefinition;
027 import org.apache.camel.model.PipelineDefinition;
028 import org.apache.camel.model.ProcessorDefinition;
029 import org.apache.camel.model.RouteDefinition;
030 import org.apache.camel.model.ToDefinition;
031
032 import static org.apache.camel.util.ObjectHelper.isNotEmpty;
033 /**
034 * A <a href="http://www.graphviz.org/">DOT</a> file creator plugin which
035 * creates a DOT file showing the current routes
036 *
037 * @version $Revision: 751357 $
038 */
039 public class RouteDotGenerator extends GraphGeneratorSupport {
040
041 public RouteDotGenerator(String dir) {
042 super(dir, ".dot");
043 }
044
045 // Implementation methods
046 //-------------------------------------------------------------------------
047
048 protected void printRoutes(PrintWriter writer, Map<String, List<RouteDefinition>> map) {
049 Set<Map.Entry<String, List<RouteDefinition>>> entries = map.entrySet();
050 for (Map.Entry<String, List<RouteDefinition>> entry : entries) {
051 String group = entry.getKey();
052 printRoutes(writer, group, entry.getValue());
053 }
054 }
055
056 protected void printRoutes(PrintWriter writer, String group, List<RouteDefinition> routes) {
057 if (group != null) {
058 writer.println("subgraph cluster_" + (clusterCounter++) + " {");
059 writer.println("label = \"" + group + "\";");
060 writer.println("color = grey;");
061 writer.println("style = \"dashed\";");
062 writer.println("URL = \"" + group + ".html\";");
063 writer.println();
064 }
065 for (RouteDefinition route : routes) {
066 List<FromDefinition> inputs = route.getInputs();
067 for (FromDefinition input : inputs) {
068 printRoute(writer, route, input);
069 }
070 writer.println();
071 }
072 if (group != null) {
073 writer.println("}");
074 writer.println();
075 }
076 }
077
078 protected String escapeNodeId(String text) {
079 return text.replace('.', '_').replace("$", "_");
080 }
081
082 protected void printRoute(PrintWriter writer, final RouteDefinition route, FromDefinition input) {
083 NodeData nodeData = getNodeData(input);
084
085 printNode(writer, nodeData);
086
087 // TODO we should add a transactional client / event driven consumer / polling client
088
089 NodeData from = nodeData;
090 for (ProcessorDefinition output : route.getOutputs()) {
091 NodeData newData = printNode(writer, from, output);
092 from = newData;
093 }
094 }
095
096 @SuppressWarnings("unchecked")
097 protected NodeData printNode(PrintWriter writer, NodeData fromData, ProcessorDefinition node) {
098 if (node instanceof MulticastDefinition || node instanceof InterceptorDefinition) {
099 // no need for a multicast or interceptor node
100 List<ProcessorDefinition> outputs = node.getOutputs();
101 boolean isPipeline = isPipeline(node);
102 for (ProcessorDefinition output : outputs) {
103 NodeData out = printNode(writer, fromData, output);
104 // if in pipeline then we should move the from node to the next in the pipeline
105 if (isPipeline) {
106 fromData = out;
107 }
108 }
109 return fromData;
110 }
111 NodeData toData = getNodeData(node);
112
113 printNode(writer, toData);
114
115 if (fromData != null) {
116 writer.print(fromData.id);
117 writer.print(" -> ");
118 writer.print(toData.id);
119 writer.println(" [");
120
121 String label = fromData.edgeLabel;
122 if (isNotEmpty(label)) {
123 writer.println("label = \"" + label + "\"");
124 }
125 writer.println("];");
126 }
127
128 // now lets write any children
129 //List<ProcessorType> outputs = node.getOutputs();
130 List<ProcessorDefinition> outputs = toData.outputs;
131 if (outputs != null) {
132 for (ProcessorDefinition output : outputs) {
133 NodeData newData = printNode(writer, toData, output);
134 if (!isMulticastNode(node)) {
135 toData = newData;
136 }
137 }
138 }
139 return toData;
140 }
141
142 protected void printNode(PrintWriter writer, NodeData data) {
143 if (!data.nodeWritten) {
144 data.nodeWritten = true;
145
146 writer.println();
147 writer.print(data.id);
148 writer.println(" [");
149 writer.println("label = \"" + data.label + "\"");
150 writer.println("tooltip = \"" + data.tooltop + "\"");
151 if (data.url != null) {
152 writer.println("URL = \"" + data.url + "\"");
153 }
154
155 String image = data.image;
156 if (image != null) {
157 writer.println("shapefile = \"" + image + "\"");
158 writer.println("peripheries=0");
159 }
160 String shape = data.shape;
161 if (shape == null && image != null) {
162 shape = "custom";
163 }
164 if (shape != null) {
165 writer.println("shape = \"" + shape + "\"");
166 }
167 writer.println("];");
168 writer.println();
169 }
170 }
171
172 protected void generateFile(PrintWriter writer, Map<String, List<RouteDefinition>> map) {
173 writer.println("digraph CamelRoutes {");
174 writer.println();
175
176 writer.println("node [style = \"rounded,filled\", fillcolor = yellow, "
177 + "fontname=\"Helvetica-Oblique\"];");
178 writer.println();
179 printRoutes(writer, map);
180
181 writer.println("}");
182 }
183
184 /**
185 * Is the given node a pipeline
186 */
187 private static boolean isPipeline(ProcessorDefinition node) {
188 if (node instanceof MulticastDefinition) {
189 return false;
190 }
191 if (node instanceof PipelineDefinition) {
192 return true;
193 }
194 if (node.getOutputs().size() > 1) {
195 // is pipeline if there is more than 1 output and they are all To types
196 for (Object type : node.getOutputs()) {
197 if (!(type instanceof ToDefinition)) {
198 return false;
199 }
200 }
201 return true;
202 }
203 return false;
204 }
205
206 }