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 }