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    }