/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.maven;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
/**
* Runs Camel embedded with META-INF/services/*.xml spring files to try create DOT files for the
* routing rules, then converts the DOT files into another format such as PNG
*
* @version $Revision: 1.1 $
* @goal dot
* @requiresDependencyResolution runtime
* @phase prepare-package
* @execute phase="test-compile"
* @see <a href="http://www.graphviz.org/">GraphViz</a>
*/
public class DotMojo extends AbstractMavenReport {
public static final String[] DEFAULT_GRAPHVIZ_OUTPUT_TYPES = {"png", "svg", "cmapx"};
/**
* Subdirectory for report.
*/
protected static final String SUBDIRECTORY = "cameldoc";
private String indexHtmlContent;
/**
* Reference to Maven 2 Project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* Base output directory.
*
* @parameter expression="${project.build.directory}"
* @required
* @readonly
*/
private File buildDirectory;
/**
* Base output directory for reports.
*
* @parameter default-value="${project.build.directory}/site/cameldoc"
* @readonly
* @required
*/
private File outputDirectory;
/**
* GraphViz executable location; visualization (images) will be
* generated only if you install this program and set this property to the
* executable dot (dot.exe on Win).
*
* @parameter expression="dot"
*/
private String executable;
/**
* Graphviz output types. Default is png. Possible values: png, jpg, gif, svg.
*
* @required
*/
private String graphvizOutputType;
/**
* Graphviz output types. Possible values: png, jpg, gif, svg.
*
* @parameter
*/
private String[] graphvizOutputTypes;
/**
* Doxia SiteRender.
*
* @component
*/
private Renderer renderer;
//
// For running Camel embedded
//-------------------------------------------------------------------------
//
/**
* The duration to run the application for which by default is in milliseconds.
*
* @parameter expression="2s"
* @readonly
*/
protected String duration;
/**
* Whether we should boot up camel with the META-INF/services/*.xml to generate the DOT file
*
* @parameter expression="true"
* @readonly
*/
protected boolean runCamel;
/**
* @param locale report locale.
* @return report description.
* @see org.apache.maven.reporting.MavenReport#getDescription(Locale)
*/
public String getDescription(final Locale locale) {
return getBundle(locale).getString("report.description");
}
/**
* @see org.apache.maven.reporting.MavenReport#getName(Locale)
*/
public String getName(final Locale locale) {
return getBundle(locale).getString("report.name");
}
/**
* @see org.apache.maven.reporting.MavenReport#getOutputName()
*/
public String getOutputName() {
return SUBDIRECTORY + "/index";
}
/**
* @see org.apache.maven.plugin.Mojo#execute()
*/
public void execute() throws MojoExecutionException {
this.execute(this.buildDirectory, Locale.getDefault());
try {
writeIndexHtmlFile("index.html", indexHtmlContent);
}
catch (IOException e) {
throw new MojoExecutionException("Failed: " + e, e);
}
}
/**
* @see org.apache.maven.reporting.AbstractMavenReport#executeReport(Locale)
*/
protected void executeReport(final Locale locale) throws MavenReportException {
try {
this.execute(this.outputDirectory, locale);
Sink kitchenSink = getSink();
if (kitchenSink != null) {
kitchenSink.rawText(indexHtmlContent);
}
else {
writeIndexHtmlFile("index.html", indexHtmlContent);
}
}
catch (Exception e) {
final MavenReportException ex = new MavenReportException(e.getMessage());
ex.initCause(e.getCause());
throw ex;
}
}
/**
* Executes DOT generator.
*
* @param outputDir report output directory.
* @param locale report locale.
* @throws MojoExecutionException if there were any execution errors.
*/
protected void execute(final File outputDir, final Locale locale) throws MojoExecutionException {
try {
runCamelEmbedded(outputDir);
}
catch (DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Failed: " + e, e);
}
outputDir.mkdirs();
List<File> files = new ArrayList<File>();
appendFiles(files, outputDirectory);
if (graphvizOutputTypes == null) {
if (graphvizOutputType == null) {
graphvizOutputTypes = DEFAULT_GRAPHVIZ_OUTPUT_TYPES;
}
else {
graphvizOutputTypes = new String[]{graphvizOutputType};
}
}
try {
for (int i = 0; i < files.size(); i++) {
File file = (File) ((List) files).get(i);
StringWriter buffer = new StringWriter();
PrintWriter out = new PrintWriter(buffer);
printHtmlHeader(out);
printHtmlFileHeader(out, file);
for (int j = 0; j < graphvizOutputTypes.length; j++) {
String format = graphvizOutputTypes[j];
String generated = convertFile(file, format);
if (format.equals("cmapx")) {
// lets include the generated file inside the html
addFileToBuffer(out, new File(generated));
}
}
printHtmlFileFooter(out, file);
printHtmlFooter(out);
String content = buffer.toString();
String name = file.getName();
if (name.equalsIgnoreCase("routes.dot") || i == 0) {
indexHtmlContent = content;
}
int idx = name.lastIndexOf(".");
if (idx >= 0) {
name = name.substring(0, idx);
name += ".html";
}
writeIndexHtmlFile(name, content);
}
}
catch (CommandLineException e) {
throw new MojoExecutionException("Failed: " + e, e);
}
catch (IOException e) {
throw new MojoExecutionException("Failed: " + e, e);
}
}
protected void runCamelEmbedded(File outputDir) throws DependencyResolutionRequiredException {
if (runCamel) {
getLog().info("Running Camel embedded to load META-INF/spring/*.xml files");
List list = project.getTestClasspathElements();
getLog().debug("Using classpath: " + list);
EmbeddedMojo mojo = new EmbeddedMojo();
mojo.setClasspathElements(list);
mojo.setDotEnabled(true);
mojo.setOutputDirectory(outputDirectory.getAbsolutePath());
mojo.setDuration(duration);
mojo.setLog(getLog());
mojo.setPluginContext(getPluginContext());
try {
mojo.executeWithoutWrapping();
}
catch (Exception e) {
getLog().error("Failed to run Camel embedded: " + e, e);
}
}
}
protected void writeIndexHtmlFile(String fileName, String content) throws IOException {
//File dir = new File(outputDirectory, SUBDIRECTORY);
File dir = outputDirectory;
dir.mkdirs();
File html = new File(dir, fileName);
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter(html));
out.println("<html>");
out.println("<head>");
out.println("</head>");
out.println("<body>");
out.println();
if (content == null) {
out.write("<p>No EIP diagrams available</p>");
}
else {
out.write(content);
}
out.println("</body>");
out.println("</html>");
}
finally {
String description = "Failed to close html output file";
close(out, description);
}
}
protected void printHtmlHeader(PrintWriter out) {
out.println("<h1>Camel EIP Patterns</h1>");
out.println();
}
protected void printHtmlFileHeader(PrintWriter out, File file) {
out.println("<p>");
out.println(" <img src='" + removeFileExtension(file.getName()) + ".png' usemap='#CamelRoutes'>");
}
protected void printHtmlFileFooter(PrintWriter out, File file) {
out.println(" </img>");
out.println("</p>");
out.println();
}
protected void printHtmlFooter(PrintWriter out) {
out.println();
}
protected void close(Closeable closeable, String description) {
if (closeable != null) {
try {
closeable.close();
}
catch (IOException e) {
getLog().warn(description + ": " + e);
}
}
}
protected String convertFile(File file, String format) throws CommandLineException {
String generatedFileName = removeFileExtension(file.getAbsolutePath()) + "." + format;
Log log = getLog();
Commandline cl = new Commandline();
cl.setExecutable(executable);
cl.createArgument().setValue("-T" + format);
cl.createArgument().setValue("-o");
cl.createArgument().setValue(generatedFileName);
cl.createArgument().setValue(file.getAbsolutePath());
log.debug("executing: " + cl.toString());
CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
int exitCode = CommandLineUtils.executeCommandLine(cl, stdout, stderr);
String output = stdout.getOutput();
if (output.length() > 0) {
log.debug(output);
}
String errOutput = stderr.getOutput();
if (errOutput.length() > 0) {
log.warn(errOutput);
}
return generatedFileName;
}
protected String removeFileExtension(String name) {
int idx = name.lastIndexOf(".");
if (idx > 0) {
return name.substring(0, idx);
}
else {
return name;
}
}
private void appendFiles(List<File> output, File file) {
if (file.isDirectory()) {
appendDirectory(output, file);
}
else {
if (isValid(file)) {
output.add(file);
}
}
}
private void appendDirectory(List<File> output, File dir) {
File[] files = dir.listFiles();
for (File file : files) {
appendFiles(output, file);
}
}
private boolean isValid(File file) {
String name = file.getName().toLowerCase();
return name.endsWith(".dot");
}
private void addFileToBuffer(PrintWriter out, File file) throws MojoExecutionException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
else {
out.println(line);
}
}
}
catch (IOException e) {
throw new MojoExecutionException("Failed: " + e, e);
}
finally {
close(reader, "cmapx file");
}
}
/**
* Gets resource bundle for given locale.
*
* @param locale locale
* @return resource bundle
*/
protected ResourceBundle getBundle(final Locale locale) {
return ResourceBundle.getBundle(
"camel-maven-plugin",
locale,
this.getClass().getClassLoader());
}
protected Renderer getSiteRenderer() {
return this.renderer;
}
/**
* @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
*/
protected String getOutputDirectory() {
return this.outputDirectory.getAbsolutePath();
}
protected MavenProject getProject() {
return this.project;
}
}