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.guice.maven;
018    
019    import java.io.BufferedReader;
020    import java.io.Closeable;
021    import java.io.File;
022    import java.io.FileReader;
023    import java.io.FileWriter;
024    import java.io.IOException;
025    import java.io.PrintWriter;
026    import java.io.StringWriter;
027    import java.util.ArrayList;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Locale;
031    import java.util.ResourceBundle;
032    import java.util.Set;
033    
034    import org.apache.maven.artifact.DependencyResolutionRequiredException;
035    import org.apache.maven.doxia.sink.Sink;
036    import org.apache.maven.doxia.siterenderer.Renderer;
037    import org.apache.maven.plugin.MojoExecutionException;
038    import org.apache.maven.plugin.logging.Log;
039    import org.apache.maven.project.MavenProject;
040    import org.apache.maven.reporting.AbstractMavenReport;
041    import org.apache.maven.reporting.MavenReportException;
042    import org.codehaus.plexus.util.cli.CommandLineException;
043    import org.codehaus.plexus.util.cli.CommandLineUtils;
044    import org.codehaus.plexus.util.cli.Commandline;
045    
046    /**
047     * Runs a Camel using the
048     * <code>jndi.properties</code> file on the classpath to
049     * way to <a href="http://camel.apache.org/guice.html">bootstrap via Guice</a>
050     * then the DOT files are created, then they are converted from DOT files to another format such as PNG
051     *
052     * @version $Revision: 2427 $
053     * @goal dot
054     * @requiresDependencyResolution runtime
055     * @phase prepare-package
056     * @execute phase="test-compile"
057     * @see <a href="http://www.graphviz.org/">GraphViz</a>
058     */
059    public class DotMojo extends AbstractMavenReport {
060        public static final String[] DEFAULT_GRAPHVIZ_OUTPUT_TYPES = {"png", "svg", "cmapx"};
061        /**
062         * Subdirectory for report.
063         */
064        protected static final String SUBDIRECTORY = "cameldoc";
065        //
066        // For running Camel embedded
067        // -------------------------------------------------------------------------
068        //
069        /**
070         * The duration to run the application for which by default is in
071         * milliseconds. A value <= 0 will run forever.
072         * Adding a s indicates seconds - eg "5s" means 5 seconds.
073         *
074         * @parameter expression="2s"
075         */
076        protected String duration;
077    
078        /**
079         * Whether we should boot up camel with the jndi.properties file to
080         * generate the DOT file
081         *
082         * @parameter expression="true"
083         */
084        protected boolean runCamel;
085    
086        /**
087         * Should we try run the DOT executable on the generated .DOT file to
088         * generate images
089         *
090         * @parameter expression="true"
091         */
092        protected boolean useDot;
093    
094        /**
095         * The main class to execute.
096         *
097         * @parameter expression="${camel.mainClass}"
098         *            default-value="org.apache.camel.guice.Main"
099         * @required
100         */
101        private String mainClass;
102    
103        /**
104         * Reference to Maven 2 Project.
105         *
106         * @parameter expression="${project}"
107         * @required
108         * @readonly
109         */
110        private MavenProject project;
111    
112        /**
113         * Base output directory.
114         *
115         * @parameter expression="${project.build.directory}"
116         * @required
117         */
118        private File buildDirectory;
119    
120        /**
121         * Base output directory for reports.
122         *
123         * @parameter default-value="${project.build.directory}/site/cameldoc"
124         * @required
125         */
126        private File outputDirectory;
127    
128    
129        /**
130         * In the case of multiple camel contexts, setting aggregate == true will
131         * aggregate all into a monolithic context, otherwise they will be processed
132         * independently.
133         *
134         * @parameter
135         */
136        private String aggregate;
137    
138        /**
139         * GraphViz executable location; visualization (images) will be generated
140         * only if you install this program and set this property to the executable
141         * dot (dot.exe on Win).
142         *
143         * @parameter expression="dot"
144         */
145        private String executable;
146    
147        /**
148         * Graphviz output types. Default is png. Possible values: png, jpg, gif,
149         * svg.
150         *
151         * @required
152         */
153        private String graphvizOutputType;
154    
155        /**
156         * Graphviz output types. Possible values: png, jpg, gif, svg.
157         *
158         * @parameter
159         */
160        private String[] graphvizOutputTypes;
161    
162        /**
163         * Doxia SiteRender.
164         *
165         * @component
166         */
167        private Renderer renderer;
168    
169        private String indexHtmlContent;
170    
171        /**
172         * @param locale report locale.
173         * @return report description.
174         * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
175         */
176        public String getDescription(final Locale locale) {
177            return getBundle(locale).getString("report.dot.description");
178        }
179    
180        /**
181         * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
182         */
183        public String getName(final Locale locale) {
184            return getBundle(locale).getString("report.dot.name");
185        }
186    
187        public String getOutputName() {
188            return SUBDIRECTORY + "/index";
189        }
190    
191        public String getAggregate() {
192            return aggregate;
193        }
194    
195        public void setAggregate(String aggregate) {
196            this.aggregate = aggregate;
197        }
198    
199        public boolean isUseDot() {
200            return useDot;
201        }
202    
203        public void setUseDot(boolean useDot) {
204            this.useDot = useDot;
205        }
206    
207        public void execute() throws MojoExecutionException {
208            this.execute(this.buildDirectory, Locale.getDefault());
209            try {
210                writeIndexHtmlFile(outputDirectory, "index.html", indexHtmlContent);
211            } catch (IOException e) {
212                throw new MojoExecutionException("Failed: " + e, e);
213            }
214        }
215    
216        protected void executeReport(final Locale locale) throws MavenReportException {
217            try {
218                this.execute(this.outputDirectory, locale);
219    
220                Sink kitchenSink = getSink();
221                if (kitchenSink != null) {
222                    kitchenSink.rawText(indexHtmlContent);
223                } else {
224                    writeIndexHtmlFile(outputDirectory, "index.html", indexHtmlContent);
225                }
226            } catch (Exception e) {
227                final MavenReportException ex = new MavenReportException(e.getMessage());
228                ex.initCause(e.getCause());
229                throw ex;
230            }
231        }
232    
233        /**
234         * Executes DOT generator.
235         *
236         * @param outputDir report output directory.
237         * @param locale report locale.
238         * @throws org.apache.maven.plugin.MojoExecutionException if there were any execution errors.
239         */
240        protected void execute(final File outputDir, final Locale locale) throws MojoExecutionException {
241    
242            try {
243                runCamelEmbedded(outputDir);
244            } catch (DependencyResolutionRequiredException e) {
245                throw new MojoExecutionException("Failed: " + e, e);
246            }
247            outputDir.mkdirs();
248    
249            List<File> files = new ArrayList<File>();
250            appendFiles(files, outputDirectory);
251    
252            if (graphvizOutputTypes == null) {
253                if (graphvizOutputType == null) {
254                    graphvizOutputTypes = DEFAULT_GRAPHVIZ_OUTPUT_TYPES;
255                } else {
256                    graphvizOutputTypes = new String[] {graphvizOutputType};
257                }
258            }
259            try {
260                Set<String> contextNames = new HashSet<String>();
261                for (File file : files) {
262                    String contextName = file.getParentFile().getName();
263                    contextNames.add(contextName);
264                }
265    
266                boolean multipleCamelContexts = contextNames.size() > 1;
267                int size = files.size();
268                for (int i = 0; i < size; i++) {
269                    File file = files.get(i);
270                    String contextName = null;
271                    if (multipleCamelContexts) {
272                        contextName = file.getParentFile().getName();
273                    }
274    
275                    getLog().info("Generating contextName: " + contextName + " file: " + file + "");
276    
277                    generate(i, file, contextName);
278                }
279    
280                if (multipleCamelContexts) {
281                    // lets generate an index page which lists each indiviual
282                    // CamelContext file
283                    StringWriter buffer = new StringWriter();
284                    PrintWriter out = new PrintWriter(buffer);
285    
286                    out.println("<h1>Camel Contexts</h1>");
287                    out.println();
288    
289                    out.println("<ul>");
290                    for (String contextName : contextNames) {
291                        out.print("  <li><a href='");
292                        out.print(contextName);
293                        out.print("/routes.html'>");
294                        out.print(contextName);
295                        out.println("</a></li>");
296                    }
297                    out.println("</ul>");
298                    indexHtmlContent = buffer.toString();
299                }
300            } catch (CommandLineException e) {
301                throw new MojoExecutionException("Failed: " + e, e);
302            } catch (IOException e) {
303                throw new MojoExecutionException("Failed: " + e, e);
304            }
305        }
306    
307        private void generate(int index, File file, String contextName) throws CommandLineException,
308            MojoExecutionException, IOException {
309    
310            StringWriter buffer = new StringWriter();
311            PrintWriter out = new PrintWriter(buffer);
312            printHtmlHeader(out, contextName);
313            printHtmlFileHeader(out, file);
314            for (int j = 0; j < graphvizOutputTypes.length; j++) {
315                String format = graphvizOutputTypes[j];
316                String generated = convertFile(file, format);
317    
318                if (format.equals("cmapx") && generated != null) {
319                    // lets include the generated file inside the html
320                    addFileToBuffer(out, new File(generated));
321                }
322            }
323            printHtmlFileFooter(out, file);
324            printHtmlFooter(out);
325    
326            String content = buffer.toString();
327            String name = file.getName();
328            if (name.equalsIgnoreCase("routes.dot") || index == 0) {
329                indexHtmlContent = content;
330            }
331            int idx = name.lastIndexOf(".");
332            if (idx >= 0) {
333                name = name.substring(0, idx);
334                name += ".html";
335            }
336            writeIndexHtmlFile(file.getParentFile(), name, content);
337        }
338    
339        protected void runCamelEmbedded(File outputDir) throws DependencyResolutionRequiredException {
340            if (runCamel) {
341                getLog().info("Running Camel embedded to load jndi.properties file from the classpath");
342    
343                List list = project.getTestClasspathElements();
344                getLog().debug("Using classpath: " + list);
345    
346                EmbeddedMojo mojo = new EmbeddedMojo();
347                mojo.setClasspathElements(list);
348                mojo.setDotEnabled(true);
349                mojo.setMainClass(mainClass);
350                if ("true".equals(getAggregate())) {
351                    mojo.setDotAggregationEnabled(true);
352                }
353                mojo.setOutputDirectory(outputDirectory.getAbsolutePath());
354                mojo.setDuration(duration);
355                mojo.setLog(getLog());
356                mojo.setPluginContext(getPluginContext());
357                try {
358                    mojo.executeWithoutWrapping();
359                } catch (Exception e) {
360                    getLog().error("Failed to run Camel embedded: " + e, e);
361                }
362            }
363        }
364    
365        protected void writeIndexHtmlFile(File dir, String fileName, String content) throws IOException {
366            // File dir = outputDirectory;
367            dir.mkdirs();
368            File html = new File(dir, fileName);
369            PrintWriter out = null;
370            try {
371                out = new PrintWriter(new FileWriter(html));
372                out.println("<html>");
373                out.println("<head>");
374                out.println("</head>");
375                out.println("<body>");
376                out.println();
377                if (content == null) {
378                    out.write("<p>No EIP diagrams available</p>");
379                } else {
380                    out.write(content);
381                }
382                out.println("</body>");
383                out.println("</html>");
384            } finally {
385                String description = "Failed to close html output file";
386                close(out, description);
387            }
388        }
389    
390        protected void printHtmlHeader(PrintWriter out, String contextName) {
391            if (contextName != null) {
392                out.println("<h1>EIP Patterns for CamelContext: " + contextName + "</h1>");
393            } else {
394                out.println("<h1>Camel EIP Patterns</h1>");
395            }
396            out.println();
397        }
398    
399        protected void printHtmlFileHeader(PrintWriter out, File file) {
400            out.println("<p>");
401            out.println("  <img src='" + removeFileExtension(file.getName()) + ".png' usemap='#CamelRoutes'>");
402        }
403    
404        protected void printHtmlFileFooter(PrintWriter out, File file) {
405            out.println("  </img>");
406            out.println("</p>");
407            out.println();
408        }
409    
410        protected void printHtmlFooter(PrintWriter out) {
411            out.println();
412        }
413    
414        protected void close(Closeable closeable, String description) {
415            if (closeable != null) {
416                try {
417                    closeable.close();
418                } catch (IOException e) {
419                    getLog().warn(description + ": " + e);
420                }
421            }
422        }
423    
424        protected String convertFile(File file, String format) throws CommandLineException {
425            Log log = getLog();
426            if (!useDot) {
427                log.info("DOT generation disabled");
428                return null;
429            }
430            if (this.executable == null || this.executable.length() == 0) {
431                log.warn("Parameter <executable/> was not set in the pom.xml.  Skipping conversion.");
432                return null;
433            }
434    
435            String generatedFileName = removeFileExtension(file.getAbsolutePath()) + "." + format;
436            Commandline cl = new Commandline();
437            cl.setExecutable(executable);
438            cl.createArgument().setValue("-T" + format);
439            cl.createArgument().setValue("-o");
440            cl.createArgument().setValue(generatedFileName);
441            cl.createArgument().setValue(file.getAbsolutePath());
442    
443            log.debug("executing: " + cl.toString());
444    
445            CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
446            CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
447    
448            int exitCode = CommandLineUtils.executeCommandLine(cl, stdout, stderr);
449    
450            String output = stdout.getOutput();
451            if (output.length() > 0) {
452                log.debug(output);
453            }
454            String errOutput = stderr.getOutput();
455            if (errOutput.length() > 0) {
456                log.warn(errOutput);
457            }
458            return generatedFileName;
459        }
460    
461        protected String removeFileExtension(String name) {
462            int idx = name.lastIndexOf(".");
463            if (idx > 0) {
464                return name.substring(0, idx);
465            } else {
466                return name;
467            }
468        }
469    
470        private void appendFiles(List<File> output, File file) {
471            if (file.isDirectory()) {
472                appendDirectory(output, file);
473            } else {
474                if (isValid(file)) {
475                    output.add(file);
476                }
477            }
478        }
479    
480        private void appendDirectory(List<File> output, File dir) {
481            File[] files = dir.listFiles();
482            for (File file : files) {
483                appendFiles(output, file);
484            }
485        }
486    
487        private boolean isValid(File file) {
488            String name = file.getName().toLowerCase();
489            return name.endsWith(".dot");
490        }
491    
492        private void addFileToBuffer(PrintWriter out, File file) throws MojoExecutionException {
493            BufferedReader reader = null;
494            try {
495                reader = new BufferedReader(new FileReader(file));
496                while (true) {
497                    String line = reader.readLine();
498                    if (line == null) {
499                        break;
500                    } else {
501                        out.println(line);
502                    }
503                }
504            } catch (IOException e) {
505                throw new MojoExecutionException("Failed: " + e, e);
506            } finally {
507                close(reader, "cmapx file");
508            }
509        }
510    
511        /**
512         * Gets resource bundle for given locale.
513         *
514         * @param locale locale
515         * @return resource bundle
516         */
517        protected ResourceBundle getBundle(final Locale locale) {
518            return ResourceBundle.getBundle("camel-maven-plugin", locale, this.getClass().getClassLoader());
519        }
520    
521        protected Renderer getSiteRenderer() {
522            return this.renderer;
523        }
524    
525        protected String getOutputDirectory() {
526            return this.outputDirectory.getAbsolutePath();
527        }
528    
529        protected MavenProject getProject() {
530            return this.project;
531        }
532    }