You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2022/06/24 17:05:18 UTC

[camel] 01/01: CAMEL-18223: camel-plugin - Propose a debug goal to enable the textual route debugger

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 3ed3ee0591e87a1d1c72281c3f6cb912736bdd05
Author: Nicolas Filotto <nf...@talend.com>
AuthorDate: Fri Jun 24 18:53:28 2022 +0200

    CAMEL-18223: camel-plugin - Propose a debug goal to enable 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 {