CAMEL-11296: camel-maven-plugin:validate - Allow to detect duplicate route ids
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a724619a Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a724619a Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a724619a Branch: refs/heads/master Commit: a724619a0622b1747a146138d7fbe0243f9a3bf1 Parents: 76ec4bb Author: Claus Ibsen <davscl...@apache.org> Authored: Sun May 21 10:33:41 2017 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Sun May 21 10:40:57 2017 +0200 ---------------------------------------------------------------------- .../apache/camel/parser/RouteBuilderParser.java | 39 ++++++ .../org/apache/camel/parser/XmlRouteParser.java | 6 +- .../camel/parser/xml/DuplicateRouteIdsTest.java | 2 +- .../src/main/docs/camel-maven-plugin.adoc | 9 +- .../org/apache/camel/maven/ValidateMojo.java | 132 ++++++++++++++++++- 5 files changed, 178 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a724619a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java ---------------------------------------------------------------------- diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java index dd15697..d38e1d6 100644 --- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java +++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/RouteBuilderParser.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.camel.parser.helper.CamelJavaParserHelper; import org.apache.camel.parser.model.CamelEndpointDetails; +import org.apache.camel.parser.model.CamelRouteDetails; import org.apache.camel.parser.model.CamelSimpleExpressionDetails; import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.ASTNode; import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.Expression; @@ -270,6 +271,44 @@ public final class RouteBuilderParser { } } + /** + * Parses the java source class to discover Camel routes with id's assigned. + * + * @param clazz the java source class + * @param baseDir the base of the source code + * @param fullyQualifiedFileName the fully qualified source code file name + * @param routes list to add discovered and parsed routes + */ + public static void parseRouteBuilderRouteIds(JavaClassSource clazz, String baseDir, String fullyQualifiedFileName, + List<CamelRouteDetails> routes) { + + MethodSource<JavaClassSource> method = CamelJavaParserHelper.findConfigureMethod(clazz); + if (method != null) { + List<ParserResult> expressions = CamelJavaParserHelper.parseCamelConsumerUris(method, true, false); + for (ParserResult result : expressions) { + // route ids is assigned in java dsl using the routeId method + if (result.isParsed() && "routeId".equals(result.getNode())) { + String fileName = fullyQualifiedFileName; + if (fileName.startsWith(baseDir)) { + fileName = fileName.substring(baseDir.length() + 1); + } + + CamelRouteDetails detail = new CamelRouteDetails(); + detail.setFileName(fileName); + detail.setClassName(clazz.getQualifiedName()); + detail.setMethodName("configure"); + int line = findLineNumber(fullyQualifiedFileName, result.getPosition()); + if (line > -1) { + detail.setLineNumber("" + line); + } + detail.setRouteId(result.getElement()); + + routes.add(detail); + } + } + } + } + private static CamelEndpointDetails findEndpointByUri(List<CamelEndpointDetails> endpoints, String uri) { for (CamelEndpointDetails detail : endpoints) { if (uri.equals(detail.getEndpointUri())) { http://git-wip-us.apache.org/repos/asf/camel/blob/a724619a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java ---------------------------------------------------------------------- diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java index 25ed77a..364cf9a 100644 --- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java +++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/XmlRouteParser.java @@ -154,15 +154,15 @@ public final class XmlRouteParser { } /** - * Parses the XML source to discover Camel routes. + * Parses the XML source to discover Camel routes with id's assigned. * * @param xml the xml file as input stream * @param baseDir the base of the source code * @param fullyQualifiedFileName the fully qualified source code file name * @param routes list to add discovered and parsed routes */ - public static void parseXmlRouteRoutes(InputStream xml, String baseDir, String fullyQualifiedFileName, - List<CamelRouteDetails> routes) throws Exception { + public static void parseXmlRouteRouteIds(InputStream xml, String baseDir, String fullyQualifiedFileName, + List<CamelRouteDetails> routes) throws Exception { // find all the endpoints (currently only <route> and within <route>) // try parse it as dom http://git-wip-us.apache.org/repos/asf/camel/blob/a724619a/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/xml/DuplicateRouteIdsTest.java ---------------------------------------------------------------------- diff --git a/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/xml/DuplicateRouteIdsTest.java b/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/xml/DuplicateRouteIdsTest.java index 1497a8d..1e2fe60 100644 --- a/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/xml/DuplicateRouteIdsTest.java +++ b/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/xml/DuplicateRouteIdsTest.java @@ -39,7 +39,7 @@ public class DuplicateRouteIdsTest { InputStream is = new FileInputStream("src/test/resources/org/apache/camel/parser/xml/myduplicateroutes.xml"); String fqn = "src/test/resources/org/apache/camel/camel/parser/xml/myduplicateroutes.xml"; String baseDir = "src/test/resources"; - XmlRouteParser.parseXmlRouteRoutes(is, baseDir, fqn, list); + XmlRouteParser.parseXmlRouteRouteIds(is, baseDir, fqn, list); for (CamelRouteDetails detail : list) { LOG.info(detail.getRouteId()); http://git-wip-us.apache.org/repos/asf/camel/blob/a724619a/tooling/maven/camel-maven-plugin/src/main/docs/camel-maven-plugin.adoc ---------------------------------------------------------------------- diff --git a/tooling/maven/camel-maven-plugin/src/main/docs/camel-maven-plugin.adoc b/tooling/maven/camel-maven-plugin/src/main/docs/camel-maven-plugin.adoc index 01787e4..8da3da6 100644 --- a/tooling/maven/camel-maven-plugin/src/main/docs/camel-maven-plugin.adoc +++ b/tooling/maven/camel-maven-plugin/src/main/docs/camel-maven-plugin.adoc @@ -112,7 +112,11 @@ You cannot change Spring or OSGi Blueprint `<bean>` elements. == camel:validate -For validating Camel endpoints in the source code: +For validating the source code for mis configured Camel: + +- endpoint uris +- simple expressions or predicates +- duplicate route ids Then you can run the validate goal from the command line or from within your Java editor such as IDEA or Eclipse. @@ -229,7 +233,8 @@ The maven plugin supports the following options which can be configured from the | excludes | | To filter the names of java and xml files to exclude files matching any of the given list of patterns (wildcard and regular expression). Multiple values can be separated by comma. | ignoreUnknownComponent | true | Whether to ignore unknown components. | ignoreIncapable | true | Whether to ignore incapable of parsing the endpoint uri or simple expression. -| ignoreLenientProperties | true | Whether to ignore components that uses lenient properties. When this is true, then the uri validation is stricter but would fail on properties that are not part of the component but in the uri because of using lenient properties. For example using the HTTP components to provide query parameters in the endpoint uri. +| ignoreLenientProperties | true | Whether to ignore components that uses lenient properties. When this is true, then the uri validation is stricter but would fail on properties that are not part of the component but in the uri because of using lenient properties. For example using the HTTP components to provide query parameters in the endpoint uri. +| duplicateRouteId | true | *Camel 2.20* Whether to validate for duplicate route ids. Route ids should be unique and if there are duplicates then Camel will fail to startup. | showAll | false | Whether to show all endpoints and simple expressions (both invalid and valid). |======================================== http://git-wip-us.apache.org/repos/asf/camel/blob/a724619a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/ValidateMojo.java ---------------------------------------------------------------------- diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/ValidateMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/ValidateMojo.java index 3c8eca2..f907607 100644 --- a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/ValidateMojo.java +++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/ValidateMojo.java @@ -34,6 +34,7 @@ import org.apache.camel.maven.helper.EndpointHelper; import org.apache.camel.parser.RouteBuilderParser; import org.apache.camel.parser.XmlRouteParser; import org.apache.camel.parser.model.CamelEndpointDetails; +import org.apache.camel.parser.model.CamelRouteDetails; import org.apache.camel.parser.model.CamelSimpleExpressionDetails; import org.apache.maven.model.Dependency; import org.apache.maven.model.Resource; @@ -160,6 +161,15 @@ public class ValidateMojo extends AbstractExecMojo { */ private boolean downloadVersion; + /** + * Whether to validate for duplicate route ids. Route ids should be unique and if there are duplicates + * then Camel will fail to startup. + * + * @parameter property="camel.duplicateRouteId" + * default-value="true" + */ + private boolean duplicateRouteId; + // CHECKSTYLE:OFF @Override public void execute() throws MojoExecutionException, MojoFailureException { @@ -200,6 +210,7 @@ public class ValidateMojo extends AbstractExecMojo { List<CamelEndpointDetails> endpoints = new ArrayList<>(); List<CamelSimpleExpressionDetails> simpleExpressions = new ArrayList<>(); + List<CamelRouteDetails> routeIds = new ArrayList<>(); Set<File> javaFiles = new LinkedHashSet<File>(); Set<File> xmlFiles = new LinkedHashSet<File>(); @@ -238,6 +249,7 @@ public class ValidateMojo extends AbstractExecMojo { if (matchFile(file)) { try { List<CamelEndpointDetails> fileEndpoints = new ArrayList<>(); + List<CamelRouteDetails> fileRouteIds = new ArrayList<>(); List<CamelSimpleExpressionDetails> fileSimpleExpressions = new ArrayList<>(); List<String> unparsable = new ArrayList<>(); @@ -250,10 +262,14 @@ public class ValidateMojo extends AbstractExecMojo { JavaClassSource clazz = (JavaClassSource) out; RouteBuilderParser.parseRouteBuilderEndpoints(clazz, baseDir, fqn, fileEndpoints, unparsable, includeTest); RouteBuilderParser.parseRouteBuilderSimpleExpressions(clazz, baseDir, fqn, fileSimpleExpressions); + if (duplicateRouteId) { + RouteBuilderParser.parseRouteBuilderRouteIds(clazz, baseDir, fqn, fileRouteIds); + } // add what we found in this file to the total list endpoints.addAll(fileEndpoints); simpleExpressions.addAll(fileSimpleExpressions); + routeIds.addAll(fileRouteIds); // was there any unparsable? if (logUnparseable && !unparsable.isEmpty()) { @@ -272,6 +288,7 @@ public class ValidateMojo extends AbstractExecMojo { try { List<CamelEndpointDetails> fileEndpoints = new ArrayList<>(); List<CamelSimpleExpressionDetails> fileSimpleExpressions = new ArrayList<>(); + List<CamelRouteDetails> fileRouteIds = new ArrayList<>(); // parse the xml source code and find Camel routes String fqn = file.getPath(); @@ -285,9 +302,17 @@ public class ValidateMojo extends AbstractExecMojo { XmlRouteParser.parseXmlRouteSimpleExpressions(is, baseDir, fqn, fileSimpleExpressions); is.close(); + if (duplicateRouteId) { + // need a new stream + is = new FileInputStream(file); + XmlRouteParser.parseXmlRouteRouteIds(is, baseDir, fqn, fileRouteIds); + is.close(); + } + // add what we found in this file to the total list endpoints.addAll(fileEndpoints); simpleExpressions.addAll(fileSimpleExpressions); + routeIds.addAll(fileRouteIds); } catch (Exception e) { getLog().warn("Error parsing xml file " + file + " code due " + e.getMessage(), e); } @@ -484,18 +509,117 @@ public class ValidateMojo extends AbstractExecMojo { simpleSummary = String.format("Simple validation error: (%s = passed, %s = invalid)", ok, simpleErrors); } - if (failOnError && (endpointErrors > 0 || simpleErrors > 0)) { - throw new MojoExecutionException(endpointSummary + "\n" + simpleSummary); - } - if (simpleErrors > 0) { getLog().warn(simpleSummary); } else { getLog().info(simpleSummary); } + + int duplicateRouteIdErrors = 0; + if (duplicateRouteId) { + + // filter out all non uniques + for (CamelRouteDetails detail : routeIds) { + // skip empty route ids + if (detail.getRouteId() == null || "".equals(detail.getRouteId())) { + continue; + } + int count = countRouteId(routeIds, detail.getRouteId()); + if (count > 1) { + duplicateRouteIdErrors++; + + StringBuilder sb = new StringBuilder(); + sb.append("Duplicate route id validation error at: "); + if (detail.getClassName() != null && detail.getLineNumber() != null) { + // this is from java code + sb.append(detail.getClassName()); + if (detail.getMethodName() != null) { + sb.append(".").append(detail.getMethodName()); + } + sb.append("(").append(asSimpleClassName(detail.getClassName())).append(".java:"); + sb.append(detail.getLineNumber()).append(")"); + } else if (detail.getLineNumber() != null) { + // this is from xml + String fqn = stripRootPath(asRelativeFile(detail.getFileName())); + if (fqn.endsWith(".xml")) { + fqn = fqn.substring(0, fqn.length() - 4); + fqn = asPackageName(fqn); + } + sb.append(fqn); + sb.append("(").append(asSimpleClassName(fqn)).append(".xml:"); + sb.append(detail.getLineNumber()).append(")"); + } else { + sb.append(detail.getFileName()); + } + sb.append("\n"); + sb.append("\n\t").append(detail.getRouteId()); + sb.append("\n\n"); + + getLog().warn(sb.toString()); + } else if (showAll) { + StringBuilder sb = new StringBuilder(); + sb.append("Duplicate route id validation passed at: "); + if (detail.getClassName() != null && detail.getLineNumber() != null) { + // this is from java code + sb.append(detail.getClassName()); + if (detail.getMethodName() != null) { + sb.append(".").append(detail.getMethodName()); + } + sb.append("(").append(asSimpleClassName(detail.getClassName())).append(".java:"); + sb.append(detail.getLineNumber()).append(")"); + } else if (detail.getLineNumber() != null) { + // this is from xml + String fqn = stripRootPath(asRelativeFile(detail.getFileName())); + if (fqn.endsWith(".xml")) { + fqn = fqn.substring(0, fqn.length() - 4); + fqn = asPackageName(fqn); + } + sb.append(fqn); + sb.append("(").append(asSimpleClassName(fqn)).append(".xml:"); + sb.append(detail.getLineNumber()).append(")"); + } else { + sb.append(detail.getFileName()); + } + sb.append("\n"); + sb.append("\n\t").append(detail.getRouteId()); + sb.append("\n\n"); + + getLog().info(sb.toString()); + } + } + } + + String routeIdSummary = ""; + if (duplicateRouteId) { + if (duplicateRouteIdErrors == 0) { + routeIdSummary = String.format("Duplicate route id validation success (%s = ids)", routeIds.size()); + } else { + routeIdSummary = String.format("Duplicate route id validation error: (%s = ids, %s = duplicates)", routeIds.size(), duplicateRouteIdErrors); + } + + if (duplicateRouteIdErrors > 0) { + getLog().warn(routeIdSummary); + } else { + getLog().info(routeIdSummary); + } + } + + if (failOnError && (endpointErrors > 0 || simpleErrors > 0 || duplicateRouteIdErrors > 0)) { + throw new MojoExecutionException(endpointSummary + "\n" + simpleSummary + "\n" + routeIdSummary); + } } // CHECKSTYLE:ON + private static int countRouteId(List<CamelRouteDetails> details, String routeId) { + int answer = 0; + for (CamelRouteDetails detail : details) { + if (routeId.equals(detail.getRouteId())) { + answer++; + } + } + return answer; + } + private static String findCamelVersion(MavenProject project) { Dependency candidate = null;