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.FromType;
025    import org.apache.camel.model.InterceptorRef;
026    import org.apache.camel.model.MulticastType;
027    import org.apache.camel.model.ProcessorType;
028    import org.apache.camel.model.RouteType;
029    import org.apache.camel.model.PipelineType;
030    import org.apache.camel.model.ToType;
031    
032    import static org.apache.camel.util.ObjectHelper.isNotNullAndNonEmpty;
033    
034    
035    /**
036     * A <a href="http://www.graphviz.org/">DOT</a> file creator plugin which
037     * creates a DOT file showing the current routes
038     *
039     * @version $Revision: 56920 $
040     */
041    public class RouteDotGenerator extends GraphGeneratorSupport {
042    
043        public RouteDotGenerator(String dir) {
044            super(dir, ".dot");
045        }
046    
047        // Implementation methods
048        //-------------------------------------------------------------------------
049    
050        protected void printRoutes(PrintWriter writer, Map<String, List<RouteType>> map) {
051            Set<Map.Entry<String, List<RouteType>>> entries = map.entrySet();
052            for (Map.Entry<String, List<RouteType>> entry : entries) {
053                String group = entry.getKey();
054                printRoutes(writer, group, entry.getValue());
055            }
056        }
057    
058        protected void printRoutes(PrintWriter writer, String group, List<RouteType> routes) {
059            if (group != null) {
060                writer.println("subgraph cluster_" + (clusterCounter++) + " {");
061                writer.println("label = \"" + group + "\";");
062                writer.println("color = grey;");
063                writer.println("style = \"dashed\";");
064                writer.println("URL = \"" + group + ".html\";");
065                writer.println();
066            }
067            for (RouteType route : routes) {
068                List<FromType> inputs = route.getInputs();
069                for (FromType input : inputs) {
070                    printRoute(writer, route, input);
071                }
072                writer.println();
073            }
074            if (group != null) {
075                writer.println("}");
076                writer.println();
077            }
078        }
079    
080        protected String escapeNodeId(String text) {
081            return text.replace('.', '_').replace("$", "_");
082        }
083    
084        protected void printRoute(PrintWriter writer, final RouteType route, FromType input) {
085            NodeData nodeData = getNodeData(input);
086    
087            printNode(writer, nodeData);
088    
089            // TODO we should add a transactional client / event driven consumer / polling client
090    
091            NodeData from = nodeData;
092            for (ProcessorType output : route.getOutputs()) {
093                NodeData newData = printNode(writer, from, output);
094                from = newData;
095            }
096        }
097    
098        protected NodeData printNode(PrintWriter writer, NodeData fromData, ProcessorType node) {
099            if (node instanceof MulticastType || node instanceof InterceptorRef) {
100                // no need for a multicast or interceptor node
101                List<ProcessorType> outputs = node.getOutputs();
102                boolean isPipeline = isPipeline(node);
103                for (ProcessorType output : outputs) {
104                    NodeData out = printNode(writer, fromData, output);
105                    // if in pipeline then we should move the from node to the next in the pipeline
106                    if (isPipeline) {
107                        fromData = out;
108                    }
109                }
110                return fromData;
111            }
112            NodeData toData = getNodeData(node);
113    
114            printNode(writer, toData);
115    
116            if (fromData != null) {
117                writer.print(fromData.id);
118                writer.print(" -> ");
119                writer.print(toData.id);
120                writer.println(" [");
121    
122                String label = fromData.edgeLabel;
123                if (isNotNullAndNonEmpty(label)) {
124                    writer.println("label = \"" + label + "\"");
125                }
126                writer.println("];");
127            }
128    
129            // now lets write any children
130            //List<ProcessorType> outputs = node.getOutputs();
131            List<ProcessorType> outputs = toData.outputs;
132            if (outputs != null) {
133                for (ProcessorType output : outputs) {
134                    NodeData newData = printNode(writer, toData, output);
135                    if (!isMulticastNode(node)) {
136                        toData = newData;
137                    }
138                }
139            }
140            return toData;
141        }
142    
143        protected void printNode(PrintWriter writer, NodeData data) {
144            if (!data.nodeWritten) {
145                data.nodeWritten = true;
146    
147                writer.println();
148                writer.print(data.id);
149                writer.println(" [");
150                writer.println("label = \"" + data.label + "\"");
151                writer.println("tooltip = \"" + data.tooltop + "\"");
152                if (data.url != null) {
153                    writer.println("URL = \"" + data.url + "\"");
154                }
155    
156                String image = data.image;
157                if (image != null) {
158                    writer.println("shapefile = \"" + image + "\"");
159                    writer.println("peripheries=0");
160                }
161                String shape = data.shape;
162                if (shape == null && image != null) {
163                    shape = "custom";
164                }
165                if (shape != null) {
166                    writer.println("shape = \"" + shape + "\"");
167                }
168                writer.println("];");
169                writer.println();
170            }
171        }
172    
173        protected void generateFile(PrintWriter writer, Map<String, List<RouteType>> map) {
174            writer.println("digraph CamelRoutes {");
175            writer.println();
176    
177            writer.println("node [style = \"rounded,filled\", fillcolor = yellow, "
178                    + "fontname=\"Helvetica-Oblique\"];");
179            writer.println();
180            printRoutes(writer, map);
181    
182            writer.println("}");
183        }
184    
185        /**
186         * Is the given node a pipeline
187         */
188        private static boolean isPipeline(ProcessorType node) {
189            if (node instanceof MulticastType) {
190                return false;
191            }
192            if (node instanceof PipelineType) {
193                return true;
194            }
195            if (node.getOutputs().size() > 1) {
196                // is pipeline if there is more than 1 output and they are all To types
197                for (Object type : node.getOutputs()) {
198                    if (!(type instanceof ToType)) {
199                        return false;
200                    }
201                }
202                return true;
203            }
204            return false;
205        }
206    
207    }