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