This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch CAMEL-18223/debug-goal-for-camel-maven-plugin in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3040f2e56eea7578864f51e3b1418669f8439c84 Author: Nicolas Filotto <[email protected]> AuthorDate: Fri Jun 24 18:53:28 2022 +0200 CAMEL-18223: camel-plugin - Propose a goal to debug with the textual route debugger --- .../src/main/docs/camel-maven-plugin.adoc | 13 ++ .../java/org/apache/camel/maven/DebugMojo.java | 150 ++++++++++++++++ .../main/java/org/apache/camel/maven/RunMojo.java | 196 +++++++++++---------- 3 files changed, 265 insertions(+), 94 deletions(-) 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 fb8b685806a..92a3efead96 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 @@ -114,6 +114,19 @@ The maven plugin *dev* goal supports the following options which can be configur | loggingLevel | OFF | Whether to use built-in console logging (uses log4j), which does not require to add any logging dependency to your project. However, the logging is fixed to log to the console, with a color style that is similar to Spring Boot. You can change the root logging level to: FATAL, ERROR, WARN, INFO, DEBUG, TRACE, OFF |=== +== camel:debug + +The `camel:debug` is an extension to `camel:dev` to run the Camel application in debug mode which allows to have set up automatically to be able to debug the camel routes using the camel textual route debugger. + +=== Options + +The maven plugin *debug* goal supports the following options which can be configured from the command line (use `-D` syntax), or defined in the `pom.xml` file in the `<configuration>` tag. + +|=== +| Parameter | Default Value | Description +| suspend | true | Indicates whether the message processing done by Camel should be suspended as long as a debugger is not attached. +|=== + == camel:prepare-fatjar The `camel:prepare-fatjar` goal of the Camel Maven Plugin is used to prepare your Camel application diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DebugMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DebugMojo.java new file mode 100644 index 00000000000..0fd83d3311e --- /dev/null +++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DebugMojo.java @@ -0,0 +1,150 @@ +/* + * 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.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.camel.impl.debugger.BacklogDebugger; +import org.apache.camel.util.CastUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; +import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +/** + * The maven goal allowing to automatically configure the Camel application to be able to use the Camel textual Route + * Debugger. + */ +@Mojo(name = "debug", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, + requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class DebugMojo extends DevMojo { + + /** + * Indicates whether the message processing done by Camel should be suspended as long as a debugger is not attached. + */ + @Parameter(property = "camel.suspend", defaultValue = "true") + private boolean suspend; + + @Parameter(defaultValue = "${mojoExecution}", readonly = true) + private MojoExecution mojo; + + @Parameter(defaultValue = "${session}", readonly = true) + private MavenSession session; + + @Override + protected void beforeBootstrapCamel() throws Exception { + super.beforeBootstrapCamel(); + + // Enable JMX + System.setProperty("org.apache.camel.jmx.disabled", "false"); + // Enable the suspend mode. + System.setProperty(BacklogDebugger.SUSPEND_MODE_SYSTEM_PROP_NAME, Boolean.toString(suspend)); + String suspendMode = System.getenv(BacklogDebugger.SUSPEND_MODE_ENV_VAR_NAME); + if (suspendMode != null && Boolean.parseBoolean(suspendMode) != suspend) { + throw new MojoExecutionException( + String.format( + "The environment variable %s has been set and prevents to configure the suspend mode. Please remove it first.", + BacklogDebugger.SUSPEND_MODE_ENV_VAR_NAME)); + } + } + + @Override + protected String goal() { + return "camel:debug"; + } + + @Override + protected List<Artifact> getClasspath() throws MojoExecutionException, MojoFailureException { + List<Artifact> classpath = super.getClasspath(); + if (classpath.stream().anyMatch(artifact -> "org.apache.camel".equals(artifact.getGroupId()) + && "camel-debug".equals(artifact.getArtifactId()))) { + getLog().debug("The component camel-debug has been detected in the classpath so no need to add it"); + return classpath; + } + getLog().info("The component camel-debug is not available in the classpath, it will be added automatically"); + Optional<String> camelCoreVersion = classpath.stream() + .filter(artifact -> "org.apache.camel".equals(artifact.getGroupId()) + && Objects.nonNull(artifact.getArtifactId()) && artifact.getArtifactId().startsWith("camel-core")) + .map(Artifact::getBaseVersion) + .filter(Objects::nonNull) + .findAny(); + if (camelCoreVersion.isEmpty()) { + getLog().info("The version of Camel could not be detected, the version of the plugin will be used instead"); + addCamelDebug(classpath, mojo.getVersion()); + return classpath; + } + addCamelDebug(classpath, camelCoreVersion.get()); + return classpath; + } + + /** + * Automatically retrieve the given version of camel-debug and add it to the classpath if it can be found. + * + * @param classpath the classpath to which camel-debug and its dependencies are added. + * @param version the version of camel-debug to retrieve. + */ + private void addCamelDebug(List<Artifact> classpath, String version) { + getLog().debug(String.format("Trying to retrieve the version %s of camel-debug", version)); + ArtifactResolutionRequest request = new ArtifactResolutionRequest(); + request.setResolveRoot(true); + request.setResolveTransitively(true); + request.setLocalRepository(session.getLocalRepository()); + request.setRemoteRepositories(session.getCurrentProject().getRemoteArtifactRepositories()); + request.setOffline(session.isOffline()); + request.setForceUpdate(session.getRequest().isUpdateSnapshots()); + request.setServers(session.getRequest().getServers()); + request.setMirrors(session.getRequest().getMirrors()); + request.setProxies(session.getRequest().getProxies()); + request.setManagedVersionMap(Collections.emptyMap()); + request.setArtifact( + new DefaultArtifact( + "org.apache.camel", "camel-debug", version, Artifact.SCOPE_RUNTIME, "jar", null, + new DefaultArtifactHandler("jar"))); + request.setResolutionFilter(new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME)); + ArtifactResolutionResult result = artifactResolver.resolve(request); + if (result.isSuccess()) { + getLog().info(String.format("Adding the version %s of camel-debug", version)); + classpath.addAll(CastUtils.cast(result.getArtifacts())); + return; + } + + if (result.hasMissingArtifacts()) { + getLog().warn( + String.format( + "Could not find the artifacts: %s", + result.getMissingArtifacts().stream().map(Objects::toString).collect(Collectors.joining(", ")))); + } + if (result.hasExceptions()) { + result.getExceptions().forEach( + ex -> getLog().warn(String.format("An error occurred while retrieving camel-debug: %s", ex.getMessage()))); + } + } +} diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RunMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RunMojo.java index fb1cfe51741..48659fa630e 100644 --- a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RunMojo.java +++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RunMojo.java @@ -57,6 +57,7 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.project.artifact.MavenMetadataSource; +import org.apache.maven.project.artifact.ProjectArtifact; import org.codehaus.mojo.exec.AbstractExecMojo; import org.codehaus.mojo.exec.ExecutableDependency; import org.codehaus.mojo.exec.Property; @@ -145,7 +146,7 @@ public class RunMojo extends AbstractExecMojo { protected String extendedPluginDependencyArtifactId; @Component - private ArtifactResolver artifactResolver; + protected ArtifactResolver artifactResolver; @Component private ArtifactFactory artifactFactory; @@ -668,18 +669,44 @@ public class RunMojo extends AbstractExecMojo { * @throws MojoExecutionException */ private ClassLoader getClassLoader() throws MojoExecutionException, MojoFailureException { - List<URL> classpathURLs = new ArrayList<>(); + final List<Artifact> classpath = getClasspath(); + final List<URL> classpathURLs = new ArrayList<>(classpath.size()); + try { + for (Artifact artifact : classpath) { + File file = artifact.getFile(); + if (file != null) { + classpathURLs.add(file.toURI().toURL()); + } + } + } catch (MalformedURLException e) { + throw new MojoExecutionException("Error during setting up classpath", e); + } + + if (logClasspath) { + getLog().info("Classpath:"); + for (URL url : classpathURLs) { + getLog().info(" " + url.getFile()); + } + } + return new URLClassLoader(classpathURLs.toArray(new URL[0])); + } + + /** + * @return the list of artifacts corresponding to the classpath to use when launching the application + */ + protected List<Artifact> getClasspath() throws MojoExecutionException, MojoFailureException { + final List<Artifact> classpath = new ArrayList<>(); // project classpath must be first - this.addRelevantProjectDependenciesToClasspath(classpathURLs); + this.addRelevantProjectDependenciesToClasspath(classpath); // and extra plugin classpath - this.addExtraPluginDependenciesToClasspath(classpathURLs); + this.addExtraPluginDependenciesToClasspath(classpath); // and plugin classpath last - this.addRelevantPluginDependenciesToClasspath(classpathURLs); + this.addRelevantPluginDependenciesToClasspath(classpath); if (!loggingLevel.equals("OFF")) { getLog().info("Using built-in logging level: " + loggingLevel); // and extra plugin classpath - this.addConsoleLogDependenciesToClasspath(classpathURLs); + this.addConsoleLogDependenciesToClasspath(classpath); // setup logging which can only be done by copying log4j.properties to project output to be in classpath try { String out = LOG4J_TEMPLATE.replace("@@@LOGGING_LEVEL@@@", loggingLevel); @@ -688,46 +715,36 @@ public class RunMojo extends AbstractExecMojo { throw new MojoFailureException("Error configuring loggingLevel", e); } } - - if (logClasspath) { - getLog().info("Classpath:"); - for (URL url : classpathURLs) { - getLog().info(" " + url.getFile()); - } - } - return new URLClassLoader(classpathURLs.toArray(new URL[classpathURLs.size()])); + return classpath; } /** * Add any relevant project dependencies to the classpath. Indirectly takes includePluginDependencies and * ExecutableDependency into consideration. * - * @param path classpath of {@link java.net.URL} objects + * @param classpath the list of artifacts representing the classpath to which artifacts should be + * added * @throws MojoExecutionException */ - private void addRelevantPluginDependenciesToClasspath(List<URL> path) throws MojoExecutionException { + private void addRelevantPluginDependenciesToClasspath(List<Artifact> classpath) throws MojoExecutionException { if (hasCommandlineArgs()) { arguments = parseCommandlineArgs(); } - try { - for (Artifact classPathElement : this.determineRelevantPluginDependencies()) { - // we must skip org.osgi.core, otherwise we get a - // java.lang.NoClassDefFoundError: org.osgi.vendor.framework property not set - if (classPathElement.getArtifactId().equals("org.osgi.core")) { - if (getLog().isDebugEnabled()) { - getLog().debug("Skipping org.osgi.core -> " + classPathElement.getGroupId() + "/" - + classPathElement.getArtifactId() + "/" + classPathElement.getVersion()); - } - continue; + for (Artifact classPathElement : this.determineRelevantPluginDependencies()) { + // we must skip org.osgi.core, otherwise we get a + // java.lang.NoClassDefFoundError: org.osgi.vendor.framework property not set + if (classPathElement.getArtifactId().equals("org.osgi.core")) { + if (getLog().isDebugEnabled()) { + getLog().debug("Skipping org.osgi.core -> " + classPathElement.getGroupId() + "/" + + classPathElement.getArtifactId() + "/" + classPathElement.getVersion()); } - - getLog().debug("Adding plugin dependency artifact: " + classPathElement.getArtifactId() - + " to classpath"); - path.add(classPathElement.getFile().toURI().toURL()); + continue; } - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error during setting up classpath", e); + + getLog().debug("Adding plugin dependency artifact: " + classPathElement.getArtifactId() + + " to classpath"); + classpath.add(classPathElement); } } @@ -736,100 +753,91 @@ public class RunMojo extends AbstractExecMojo { * Add any relevant project dependencies to the classpath. Indirectly takes includePluginDependencies and * ExecutableDependency into consideration. * - * @param path classpath of {@link java.net.URL} objects + * @param classpath the list of artifacts representing the classpath to which artifacts should be + * added * @throws MojoExecutionException */ - private void addExtraPluginDependenciesToClasspath(List<URL> path) throws MojoExecutionException { + private void addExtraPluginDependenciesToClasspath(List<Artifact> classpath) throws MojoExecutionException { if (extraPluginDependencyArtifactId == null && extendedPluginDependencyArtifactId == null) { return; } - try { - Set<Artifact> artifacts = new HashSet<>(this.pluginDependencies); - for (Artifact artifact : artifacts) { - if (artifact.getArtifactId().equals(extraPluginDependencyArtifactId) - || artifact.getArtifactId().equals(extendedPluginDependencyArtifactId)) { - getLog().debug("Adding extra plugin dependency artifact: " + artifact.getArtifactId() - + " to classpath"); - path.add(artifact.getFile().toURI().toURL()); - - // add the transient dependencies of this artifact - Set<Artifact> deps = resolveExecutableDependencies(artifact, true); - if (deps != null) { - for (Artifact dep : deps) { - getLog().debug("Adding extra plugin dependency artifact: " + dep.getArtifactId() - + " to classpath"); - path.add(dep.getFile().toURI().toURL()); - } + final Set<Artifact> artifacts = new HashSet<>(this.pluginDependencies); + for (Artifact artifact : artifacts) { + if (artifact.getArtifactId().equals(extraPluginDependencyArtifactId) + || artifact.getArtifactId().equals(extendedPluginDependencyArtifactId)) { + getLog().debug("Adding extra plugin dependency artifact: " + artifact.getArtifactId() + + " to classpath"); + classpath.add(artifact); + + // add the transient dependencies of this artifact + Set<Artifact> deps = resolveExecutableDependencies(artifact, true); + if (deps != null) { + for (Artifact dep : deps) { + getLog().debug("Adding extra plugin dependency artifact: " + dep.getArtifactId() + + " to classpath"); + classpath.add(dep); } } } - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error during setting up classpath", e); } } /** * Adds the JARs needed for using the built-in logging to console */ - private void addConsoleLogDependenciesToClasspath(List<URL> path) throws MojoExecutionException { - try { - Set<Artifact> artifacts = new HashSet<>(this.pluginDependencies); - for (Artifact artifact : artifacts) { - // add these loggers in the beginning so they are first - if (artifact.getArtifactId().equals("jansi")) { - // jansi for logging in color - path.add(0, artifact.getFile().toURI().toURL()); - } else if (artifact.getGroupId().equals("org.apache.logging.log4j")) { - // add log4j as this is needed - path.add(0, artifact.getFile().toURI().toURL()); - } else if (artifact.getArtifactId().equals("camel-maven-plugin")) { - // add ourselves - path.add(0, artifact.getFile().toURI().toURL()); - } + private void addConsoleLogDependenciesToClasspath(List<Artifact> classpath) { + Set<Artifact> artifacts = new HashSet<>(this.pluginDependencies); + for (Artifact artifact : artifacts) { + // add these loggers in the beginning so they are first + if (artifact.getArtifactId().equals("jansi")) { + // jansi for logging in color + classpath.add(0, artifact); + } else if (artifact.getGroupId().equals("org.apache.logging.log4j")) { + // add log4j as this is needed + classpath.add(0, artifact); + } else if (artifact.getArtifactId().equals("camel-maven-plugin")) { + // add ourselves + classpath.add(0, artifact); } - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error during setting up classpath", e); } } /** * Add any relevant project dependencies to the classpath. Takes includeProjectDependencies into consideration. * - * @param path classpath of {@link java.net.URL} objects + * @param classpath the list of artifacts representing the classpath to which artifacts should be + * added * @throws MojoExecutionException */ - private void addRelevantProjectDependenciesToClasspath(List<URL> path) throws MojoExecutionException { + private void addRelevantProjectDependenciesToClasspath(List<Artifact> classpath) throws MojoExecutionException { if (this.includeProjectDependencies) { - try { - getLog().debug("Project Dependencies will be included."); - - URL mainClasses = new File(project.getBuild().getOutputDirectory()).toURI().toURL(); - getLog().debug("Adding to classpath : " + mainClasses); - path.add(mainClasses); - - Set<Artifact> dependencies = CastUtils.cast(project.getArtifacts()); + getLog().debug("Project Dependencies will be included."); + + File mainClasses = new File(project.getBuild().getOutputDirectory()); + getLog().debug("Adding to classpath : " + mainClasses); + classpath.add( + new ProjectArtifact(project) { + @Override + public File getFile() { + return mainClasses; + } + }); - // system scope dependencies are not returned by maven 2.0. See - // MEXEC-17 - dependencies.addAll(getAllNonTestScopedDependencies()); + Set<Artifact> dependencies = CastUtils.cast(project.getArtifacts()); - for (Artifact classPathElement : dependencies) { - getLog().debug("Adding project dependency artifact: " + classPathElement.getArtifactId() - + " to classpath"); - File file = classPathElement.getFile(); - if (file != null) { - path.add(file.toURI().toURL()); - } - } + // system scope dependencies are not returned by maven 2.0. See + // MEXEC-17 + dependencies.addAll(getAllNonTestScopedDependencies()); - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error during setting up classpath", e); + for (Artifact classPathElement : dependencies) { + getLog().debug("Adding project dependency artifact: " + classPathElement.getArtifactId() + + " to classpath"); + classpath.add(classPathElement); } } else { getLog().debug("Project Dependencies will be excluded."); } - } private Collection<Artifact> getAllNonTestScopedDependencies() throws MojoExecutionException {
