You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pp...@apache.org on 2021/09/03 07:50:13 UTC

[camel-quarkus] 01/02: Allow extending an existing Catalog

This is an automated email from the ASF dual-hosted git repository.

ppalaga pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit 68be716bd4777db6889b12dc570adff3494b6934
Author: Peter Palaga <pp...@redhat.com>
AuthorDate: Thu Sep 2 21:44:21 2021 +0200

    Allow extending an existing Catalog
---
 pom.xml                                            |   3 +
 tooling/maven-plugin/pom.xml                       |  10 +
 .../quarkus/maven/AbstractDocGeneratorMojo.java    |  16 +-
 .../quarkus/maven/AbstractExtensionListMojo.java   |  97 ++++++++++
 .../quarkus/maven/CheckExtensionPagesMojo.java     |  51 ++----
 .../org/apache/camel/quarkus/maven/CqCatalog.java  | 142 ++++++++++----
 .../org/apache/camel/quarkus/maven/CqUtils.java    |  45 ++++-
 .../camel/quarkus/maven/ExtensionModule.java       |  66 +++++++
 .../org/apache/camel/quarkus/maven/PatternSet.java |  48 +++++
 .../quarkus/maven/PrepareCatalogQuarkusMojo.java   | 203 ++++++++-------------
 10 files changed, 465 insertions(+), 216 deletions(-)

diff --git a/pom.xml b/pom.xml
index 95cef43..1c0f46f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,9 @@
         <wiremock.version>2.27.2</wiremock.version>
         <zt-exec.version>1.11</zt-exec.version>
 
+        <!-- Tooling dependency versions (keep sorted alphabetically) -->
+        <maven-utils.version>0.1.0</maven-utils.version>
+
         <!-- Maven plugin versions (keep sorted alphabetically) -->
         <cq-plugin.version>0.40.0</cq-plugin.version>
         <build-helper-maven-plugin.version>3.1.0</build-helper-maven-plugin.version>
diff --git a/tooling/maven-plugin/pom.xml b/tooling/maven-plugin/pom.xml
index a2b70f4..f650245 100644
--- a/tooling/maven-plugin/pom.xml
+++ b/tooling/maven-plugin/pom.xml
@@ -46,6 +46,11 @@
                 <artifactId>quarkus-extension-processor</artifactId>
                 <version>${quarkus.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.l2x6.maven-utils</groupId>
+                <artifactId>maven-utils</artifactId>
+                <version>${maven-utils.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
     <dependencies>
@@ -88,6 +93,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.l2x6.maven-utils</groupId>
+            <artifactId>maven-utils</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
             <scope>test</scope>
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
index 7407509..c54eb2e 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
@@ -24,13 +24,12 @@ import java.util.Map;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.TemplateException;
-import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
 /**
  * Base for {@link CheckExtensionPagesMojo} and {@link UpdateExtensionDocPageMojo}.
  */
-abstract class AbstractDocGeneratorMojo extends AbstractMojo {
+abstract class AbstractDocGeneratorMojo extends AbstractExtensionListMojo {
     public static final String DEFAULT_TEMPLATES_URI_BASE = "classpath:/doc-templates";
     /**
      */
@@ -42,19 +41,6 @@ abstract class AbstractDocGeneratorMojo extends AbstractMojo {
     @Parameter(property = "camel-quarkus.basedir", defaultValue = "${project.basedir}")
     File baseDir;
 
-    /**
-     * Encoding to read and write files in the current source tree
-     */
-    @Parameter(defaultValue = "${project.build.sourceEncoding}", required = true, property = "camel-quarkus.encoding")
-    String encoding;
-    /**
-     * The root directory of the Camel Quarkus source tree.
-     *
-     * @since 1.0.0-M8
-     */
-    @Parameter(defaultValue = "${maven.multiModuleProjectDirectory}", required = true, property = "camel-quarkus.multiModuleProjectDirectory")
-    File multiModuleProjectDirectory;
-
     protected static <T extends Writer> T evalTemplate(Configuration cfg, String templateUri, Map<String, Object> model,
             T out) {
         try {
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractExtensionListMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractExtensionListMojo.java
new file mode 100644
index 0000000..4fde01d
--- /dev/null
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractExtensionListMojo.java
@@ -0,0 +1,97 @@
+/*
+ * 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.quarkus.maven;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.l2x6.maven.utils.MavenSourceTree;
+
+public abstract class AbstractExtensionListMojo extends AbstractMojo {
+
+    /**
+     * A set of regular expressions to match against artifactIdBases. The matching extensions will not be processed by
+     * this mojo.
+     *
+     * @since 0.1.0
+     */
+    @Parameter(property = "cq.skipArtifactIdBases")
+    protected List<String> skipArtifactIdBases;
+    private PatternSet skipArtifactIdBasePatterns;
+
+    /**
+     * The root directory of the Camel Quarkus source tree.
+     *
+     * @since 0.41.0
+     */
+    @Parameter(defaultValue = "${maven.multiModuleProjectDirectory}", readonly = true)
+    protected File multiModuleProjectDirectory;
+    private Path rootModuleDirectory;
+
+    /**
+     * Encoding to read and write files in the current source tree
+     *
+     * @since 0.18.0
+     */
+    @Parameter(defaultValue = "utf-8", required = true, property = "cq.encoding")
+    String encoding;
+    private Charset charset;
+
+    private MavenSourceTree tree;
+
+    Path getRootModuleDirectory() {
+        if (rootModuleDirectory == null) {
+            rootModuleDirectory = multiModuleProjectDirectory.toPath().toAbsolutePath().normalize();
+        }
+        return rootModuleDirectory;
+    }
+
+    Charset getCharset() {
+        if (charset == null) {
+            charset = Charset.forName(encoding);
+        }
+        return charset;
+    }
+
+    Stream<ExtensionModule> findExtensions() {
+        getSkipArtifactIdBases();
+        return CqUtils.findExtensions(
+                getRootModuleDirectory(),
+                getTree().getModulesByGa().values(),
+                artifactIdBase -> !skipArtifactIdBasePatterns.matchesAny(artifactIdBase));
+    }
+
+    PatternSet getSkipArtifactIdBases() {
+        if (skipArtifactIdBasePatterns == null) {
+            skipArtifactIdBasePatterns = skipArtifactIdBases == null ? PatternSet.empty() : new PatternSet(skipArtifactIdBases);
+        }
+        return skipArtifactIdBasePatterns;
+    }
+
+    public MavenSourceTree getTree() {
+        if (tree == null) {
+            tree = MavenSourceTree.of(getRootModuleDirectory().resolve("pom.xml"), getCharset());
+        }
+        return tree;
+    }
+
+}
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java
index b8fe771..8cb2377 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java
@@ -23,10 +23,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.regex.Matcher;
@@ -62,7 +60,7 @@ public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
     /**
      * The directory relative to which the catalog data is read.
      */
-    @Parameter(defaultValue = "${project.build.directory}/classes", property = "camel-quarkus.catalogBaseDir")
+    @Parameter(defaultValue = "${project.build.directory}/classes", property = "cq.catalogBaseDir")
     File catalogBaseDir;
 
     /**
@@ -72,18 +70,6 @@ public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
     File docsBaseDir;
 
     /**
-     * List of directories that contain extensions
-     */
-    @Parameter(property = "cq.extensionDirectories", required = true)
-    List<File> extensionDirectories;
-
-    /**
-     * A set of artifactIdBases that are not extensions and should be excluded from the catalog
-     */
-    @Parameter(property = "cq.skipArtifactIdBases")
-    Set<String> skipArtifactIdBases;
-
-    /**
      * The version of Camel we depend on
      */
     @Parameter(property = "camel.version")
@@ -108,9 +94,6 @@ public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
         final Path docsBasePath = docsBaseDir.toPath();
-        if (skipArtifactIdBases == null) {
-            skipArtifactIdBases = Collections.emptySet();
-        }
 
         camelBits(docsBasePath);
         extensions(docsBasePath);
@@ -186,17 +169,13 @@ public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
 
         final Set<String> artifactIdBases = new HashSet<>();
         final Set<CamelQuarkusExtension> extensions = new TreeSet<>(Comparator.comparing(e -> e.getName().get()));
-        extensionDirectories.stream()
-                .map(File::toPath)
-                .forEach(extDir -> {
-                    CqUtils.findExtensionArtifactIdBases(extDir)
-                            .filter(artifactIdBase -> !skipArtifactIdBases.contains(artifactIdBase))
-                            .forEach(artifactIdBase -> {
-                                artifactIdBases.add(artifactIdBase);
-                                final Path runtimePomXmlPath = extDir.resolve(artifactIdBase).resolve("runtime/pom.xml")
-                                        .toAbsolutePath().normalize();
-                                extensions.add(CamelQuarkusExtension.read(runtimePomXmlPath));
-                            });
+        findExtensions()
+                .forEach(ext -> {
+                    final String artifactIdBase = ext.getArtifactIdBase();
+                    artifactIdBases.add(artifactIdBase);
+                    final Path runtimePomXmlPath = ext.getExtensionDir().resolve("runtime/pom.xml")
+                            .toAbsolutePath().normalize();
+                    extensions.add(CamelQuarkusExtension.read(runtimePomXmlPath));
                 });
 
         final String extLinks = extensions.stream()
@@ -223,12 +202,14 @@ public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
 
     void replace(Path path, String replacementKey, String value) {
         try {
-            String document = new String(Files.readAllBytes(path), encoding);
-            document = replace(document, path, replacementKey, value);
-            try {
-                Files.write(path, document.getBytes(encoding));
-            } catch (IOException e) {
-                throw new RuntimeException("Could not write to " + path, e);
+            final String oldDocument = new String(Files.readAllBytes(path), encoding);
+            final String newDocument = replace(oldDocument, path, replacementKey, value);
+            if (!oldDocument.equals(newDocument)) {
+                try {
+                    Files.write(path, newDocument.getBytes(encoding));
+                } catch (IOException e) {
+                    throw new RuntimeException("Could not write to " + path, e);
+                }
             }
         } catch (IOException e) {
             throw new RuntimeException("Could not read from " + path, e);
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
index ac0663e..18dc539 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
@@ -18,12 +18,14 @@ package org.apache.camel.quarkus.maven;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -38,17 +40,20 @@ import org.apache.camel.catalog.impl.CatalogHelper;
 import org.apache.camel.tooling.model.ArtifactModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.DataFormatModel;
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.camel.tooling.model.LanguageModel;
+import org.apache.camel.tooling.model.OtherModel;
 
 public class CqCatalog {
 
     public enum Flavor {
-        camel("org.apache.camel", "camel-catalog") {
+        camel("org.apache.camel", "camel-catalog", "org/apache/camel/catalog") {
             @Override
             public RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c) {
                 return new DefaultRuntimeProvider(c);
             }
         },
-        camelQuarkus("org.apache.camel.quarkus", "camel-quarkus-catalog") {
+        camelQuarkus("org.apache.camel.quarkus", "camel-quarkus-catalog", CQ_CATALOG_DIR) {
             @Override
             public RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c) {
                 return new CqRuntimeProvider(c);
@@ -57,10 +62,12 @@ public class CqCatalog {
 
         private final String groupId;
         private final String artifactId;
+        private final String catalogDir;
 
-        private Flavor(String groupId, String artifactId) {
+        private Flavor(String groupId, String artifactId, String catalogDir) {
             this.groupId = groupId;
             this.artifactId = artifactId;
+            this.catalogDir = catalogDir;
         }
 
         public abstract RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c);
@@ -75,6 +82,8 @@ public class CqCatalog {
     }
 
     private final DefaultCamelCatalog catalog;
+    private final Flavor flavor;
+    public static final String CQ_CATALOG_DIR = "org/apache/camel/catalog/quarkus";
 
     private static final ThreadLocal<CqCatalog> threadLocalCamelCatalog = ThreadLocal.withInitial(CqCatalog::new);
 
@@ -82,16 +91,38 @@ public class CqCatalog {
         return threadLocalCamelCatalog.get();
     }
 
+    public static CqCatalog findFirstFromClassPath() {
+        try {
+            Class.forName("org.apache.camel.catalog.quarkus.QuarkusRuntimeProvider");
+            return new CqCatalog(Flavor.camelQuarkus,
+                    name -> Thread.currentThread().getContextClassLoader().getResourceAsStream(name));
+        } catch (ClassNotFoundException e) {
+            return new CqCatalog(Flavor.camel,
+                    name -> Thread.currentThread().getContextClassLoader().getResourceAsStream(name));
+        }
+    }
+
     public CqCatalog(Path baseDir, Flavor flavor) {
-        super();
+        this(flavor, name -> {
+            try {
+                return Files.newInputStream(baseDir.resolve(name));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    public CqCatalog(Flavor flavor, Function<String, InputStream> resourceLocator) {
+        this.flavor = flavor;
         final DefaultCamelCatalog c = new DefaultCamelCatalog(true);
         c.setRuntimeProvider(flavor.createRuntimeProvider(c));
-        c.setVersionManager(new CqVersionManager(c, baseDir));
+        c.setVersionManager(new CqVersionManager(c, resourceLocator));
         this.catalog = c;
     }
 
     public CqCatalog() {
         super();
+        this.flavor = Flavor.camel;
         this.catalog = new DefaultCamelCatalog(true);
     }
 
@@ -107,17 +138,26 @@ public class CqCatalog {
 
     public Stream<ArtifactModel<?>> filterModels(String cqArtifactIdBase) {
         final Predicate<ArtifactModel<?>> filter;
-        if ("core".equals(cqArtifactIdBase)) {
-            filter = model -> ("camel-base".equals(model.getArtifactId())
-                    || "camel-core-languages".equals(model.getArtifactId())) && !"csimple".equals(model.getName());
-        } else if ("csimple".equals(cqArtifactIdBase)) {
-            filter = model -> "camel-core-languages".equals(model.getArtifactId()) && "csimple".equals(model.getName());
-        } else if ("reactive-executor".equals(cqArtifactIdBase)) {
-            filter = model -> "camel-reactive-executor-vertx".equals(model.getArtifactId());
-        } else if ("qute".equals(cqArtifactIdBase)) {
-            filter = model -> "camel-quarkus-qute-component".equals(model.getArtifactId());
-        } else {
-            filter = model -> ("camel-" + cqArtifactIdBase).equals(model.getArtifactId());
+        switch (flavor) {
+        case camel:
+            if ("core".equals(cqArtifactIdBase)) {
+                filter = model -> ("camel-base".equals(model.getArtifactId())
+                        || "camel-core-languages".equals(model.getArtifactId())) && !"csimple".equals(model.getName());
+            } else if ("csimple".equals(cqArtifactIdBase)) {
+                filter = model -> "camel-core-languages".equals(model.getArtifactId()) && "csimple".equals(model.getName());
+            } else if ("reactive-executor".equals(cqArtifactIdBase)) {
+                filter = model -> "camel-reactive-executor-vertx".equals(model.getArtifactId());
+            } else if ("qute".equals(cqArtifactIdBase)) {
+                filter = model -> "camel-quarkus-qute-component".equals(model.getArtifactId());
+            } else {
+                filter = model -> ("camel-" + cqArtifactIdBase).equals(model.getArtifactId());
+            }
+            break;
+        case camelQuarkus:
+            filter = model -> ("camel-quarkus-" + cqArtifactIdBase).equals(model.getArtifactId());
+            break;
+        default:
+            throw new IllegalStateException("Unexpected " + Flavor.class.getName() + " " + flavor);
         }
         return models().filter(filter);
     }
@@ -135,6 +175,43 @@ public class CqCatalog {
         catalog.addComponent(name, className, jsonSchema);
     }
 
+    public void store(Path baseDir) {
+        models().forEach(m -> serialize(baseDir.resolve(flavor.catalogDir), m));
+    }
+
+    static void serialize(final Path catalogPath, ArtifactModel<?> model) {
+        final Path out = catalogPath.resolve(model.getKind() + "s")
+                .resolve(model.getName() + ".json");
+        try {
+            Files.createDirectories(out.getParent());
+        } catch (IOException e) {
+            throw new RuntimeException("Could not create " + out.getParent(), e);
+        }
+        String rawJson;
+        switch (Kind.valueOf(model.getKind())) {
+        case component:
+            rawJson = JsonMapper.createParameterJsonSchema((ComponentModel) model);
+            break;
+        case language:
+            rawJson = JsonMapper.createParameterJsonSchema((LanguageModel) model);
+            break;
+        case dataformat:
+            rawJson = JsonMapper.createParameterJsonSchema((DataFormatModel) model);
+            break;
+        case other:
+            rawJson = JsonMapper.createJsonSchema((OtherModel) model);
+            break;
+        default:
+            throw new IllegalStateException("Cannot serialize kind " + model.getKind());
+        }
+
+        try {
+            Files.write(out, rawJson.getBytes(StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            throw new RuntimeException("Could not write to " + out, e);
+        }
+    }
+
     public static Stream<Kind> kinds() {
         return Stream.of(Kind.values())
                 .filter(kind -> kind != org.apache.camel.catalog.Kind.eip);
@@ -192,7 +269,10 @@ public class CqCatalog {
     public static List<ArtifactModel<?>> primaryModel(Stream<ArtifactModel<?>> input) {
         final List<ArtifactModel<?>> models = input
                 .filter(CqCatalog::isFirstScheme)
-                .filter(m -> !m.getName().startsWith("google-") || !m.getName().endsWith("-stream")) // ignore the google stream component variants
+                .filter(m -> !m.getName().startsWith("google-") || !m.getName().endsWith("-stream")) // ignore the
+                // google stream
+                // component
+                // variants
                 .collect(Collectors.toList());
         if (models.size() > 1) {
             List<ArtifactModel<?>> componentModels = models.stream()
@@ -222,34 +302,30 @@ public class CqCatalog {
     }
 
     static class CqVersionManager extends DefaultVersionManager {
-        private final Path baseDir;
+        private final Function<String, InputStream> resourceLocator;
 
-        public CqVersionManager(CamelCatalog camelCatalog, Path baseDir) {
+        public CqVersionManager(CamelCatalog camelCatalog, Function<String, InputStream> resourceLocator) {
             super(camelCatalog);
-            this.baseDir = baseDir;
+            this.resourceLocator = resourceLocator;
         }
 
         @Override
         public InputStream getResourceAsStream(String name) {
-            try {
-                return Files.newInputStream(baseDir.resolve(name));
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+            return resourceLocator.apply(name);
         }
 
     }
 
     static class CqRuntimeProvider implements RuntimeProvider {
 
-        private static final String COMPONENT_DIR = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/components";
-        private static final String DATAFORMAT_DIR = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/dataformats";
-        private static final String LANGUAGE_DIR = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/languages";
-        private static final String OTHER_DIR = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/others";
-        private static final String COMPONENTS_CATALOG = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/components.properties";
-        private static final String DATA_FORMATS_CATALOG = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/dataformats.properties";
-        private static final String LANGUAGE_CATALOG = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/languages.properties";
-        private static final String OTHER_CATALOG = PrepareCatalogQuarkusMojo.CQ_CATALOG_DIR + "/others.properties";
+        private static final String COMPONENT_DIR = CQ_CATALOG_DIR + "/components";
+        private static final String DATAFORMAT_DIR = CQ_CATALOG_DIR + "/dataformats";
+        private static final String LANGUAGE_DIR = CQ_CATALOG_DIR + "/languages";
+        private static final String OTHER_DIR = CQ_CATALOG_DIR + "/others";
+        private static final String COMPONENTS_CATALOG = CQ_CATALOG_DIR + "/components.properties";
+        private static final String DATA_FORMATS_CATALOG = CQ_CATALOG_DIR + "/dataformats.properties";
+        private static final String LANGUAGE_CATALOG = CQ_CATALOG_DIR + "/languages.properties";
+        private static final String OTHER_CATALOG = CQ_CATALOG_DIR + "/others.properties";
 
         private CamelCatalog camelCatalog;
 
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
index d744903..b22e83d 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
@@ -19,13 +19,16 @@ package org.apache.camel.quarkus.maven;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.Reader;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -43,7 +46,10 @@ import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.tooling.model.LanguageModel;
 import org.apache.camel.tooling.model.OtherModel;
 import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 import org.apache.maven.plugin.logging.Log;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.l2x6.maven.utils.MavenSourceTree.Module;
 
 public class CqUtils {
     public static final String CLASSPATH_PREFIX = "classpath:";
@@ -80,16 +86,39 @@ public class CqUtils {
         }
     }
 
+    public static Stream<ExtensionModule> findExtensions(Path basePath, Collection<Module> modules,
+            Predicate<String> artifactIdBaseFilter) {
+        return modules.stream()
+                .filter(p -> p.getGav().getArtifactId().asConstant().endsWith("-deployment"))
+                .map(p -> {
+                    final Path extensionDir = basePath.resolve(p.getPomPath()).getParent().getParent().toAbsolutePath()
+                            .normalize();
+                    final String deploymentArtifactId = p.getGav().getArtifactId().asConstant();
+                    if (!deploymentArtifactId.startsWith("camel-quarkus-")) {
+                        throw new IllegalStateException("Should start with 'camel-quarkus-': " + deploymentArtifactId);
+                    }
+                    final String artifactIdBase = deploymentArtifactId.substring("camel-quarkus-".length(),
+                            deploymentArtifactId.length() - "-deployment".length());
+                    return new ExtensionModule(extensionDir, artifactIdBase);
+                })
+                .filter(e -> artifactIdBaseFilter.test(e.getArtifactIdBase()))
+                .sorted();
+    }
+
     public static Stream<String> findExtensionArtifactIdBases(Path extensionDir) {
-        try {
-            return Files.list(extensionDir)
-                    .filter(path -> Files.isDirectory(path)
-                            && Files.exists(path.resolve("pom.xml"))
-                            && Files.exists(path.resolve("runtime")))
-                    .map(dir -> dir.getFileName().toString());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+        final Path extListPomPath = extensionDir.resolve("pom.xml");
+        final List<String> modules;
+        try (Reader r = Files.newBufferedReader(extListPomPath, StandardCharsets.UTF_8)) {
+            final MavenXpp3Reader rxppReader = new MavenXpp3Reader();
+            final Model extListPom = rxppReader.read(r);
+            modules = extListPom.getModules();
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException("Could not read " + extListPomPath);
         }
+
+        return modules.stream()
+                .filter(m -> Files.isRegularFile(extensionDir.resolve(m + "/pom.xml"))
+                        && Files.isDirectory(extensionDir.resolve(m + "/runtime")));
     }
 
     public static Configuration getTemplateConfig(Path basePath, String defaultUriBase, String templatesUriBase,
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/ExtensionModule.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/ExtensionModule.java
new file mode 100644
index 0000000..4e617e8
--- /dev/null
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/ExtensionModule.java
@@ -0,0 +1,66 @@
+/*
+ * 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.quarkus.maven;
+
+import java.nio.file.Path;
+
+public class ExtensionModule implements Comparable<ExtensionModule> {
+
+    private final Path extensionDir;
+    private final String artifactIdBase;
+
+    public ExtensionModule(Path extensionDir, String artifactIdBase) {
+        super();
+        this.extensionDir = extensionDir;
+        this.artifactIdBase = artifactIdBase;
+    }
+
+    /**
+     * @return the absolute canonical path of the directory whose children are the runtime and deployment directories of
+     *         this {@link ExtensionModule}
+     */
+    public Path getExtensionDir() {
+        return extensionDir;
+    }
+
+    public String getArtifactIdBase() {
+        return artifactIdBase;
+    }
+
+    @Override
+    public String toString() {
+        return artifactIdBase + " (" + extensionDir + ")";
+    }
+
+    public boolean isNativeSupported() {
+        return !extensionDir.getParent().getFileName().toString().equals("extensions-jvm");
+    }
+
+    @Override
+    public int compareTo(ExtensionModule other) {
+        return this.artifactIdBase.compareTo(other.artifactIdBase);
+    }
+
+    public Path getRuntimePomPath() {
+        return extensionDir.resolve("runtime/pom.xml");
+    }
+
+    public Path getPomPath() {
+        return extensionDir.resolve("pom.xml");
+    }
+
+}
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PatternSet.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PatternSet.java
new file mode 100644
index 0000000..2c03ef3
--- /dev/null
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PatternSet.java
@@ -0,0 +1,48 @@
+/*
+ * 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.quarkus.maven;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class PatternSet {
+
+    private static final PatternSet EMPTY = new PatternSet(Collections.emptyList());
+
+    public static PatternSet empty() {
+        return EMPTY;
+    }
+
+    private final List<Pattern> patterns;
+
+    PatternSet(Collection<String> rawPatterns) {
+        this.patterns = rawPatterns.stream()
+                .map(Pattern::compile)
+                .collect(Collectors.toList());
+    }
+
+    public boolean matchesAny(String string) {
+        if (patterns.isEmpty()) {
+            return false;
+        }
+        return patterns.stream()
+                .anyMatch(pat -> pat.matcher(string).matches());
+    }
+}
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareCatalogQuarkusMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareCatalogQuarkusMojo.java
index dc19c29..c0e9c45 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareCatalogQuarkusMojo.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareCatalogQuarkusMojo.java
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -33,11 +32,9 @@ import org.apache.camel.catalog.Kind;
 import org.apache.camel.tooling.model.ArtifactModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.DataFormatModel;
-import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.tooling.model.LanguageModel;
 import org.apache.camel.tooling.model.OtherModel;
 import org.apache.camel.tooling.model.SupportLevel;
-import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Mojo;
@@ -49,9 +46,8 @@ import org.apache.maven.plugins.annotations.Parameter;
  * @since 0.1.0
  */
 @Mojo(name = "prepare-catalog-quarkus", threadSafe = true)
-public class PrepareCatalogQuarkusMojo extends AbstractMojo {
+public class PrepareCatalogQuarkusMojo extends AbstractExtensionListMojo {
 
-    public static final String CQ_CATALOG_DIR = "org/apache/camel/catalog/quarkus";
     public static final String CAMEL_ARTIFACT = "camelArtifact";
     /**
      * The output directory where the catalog files should be written.
@@ -60,104 +56,94 @@ public class PrepareCatalogQuarkusMojo extends AbstractMojo {
     File catalogBaseDir;
 
     /**
-     * List of directories that contain extensions
+     * If {@code true}, the Catalog available in the class path will be first dumped to {@link #catalogBaseDir}
+     * and then some of its options will be overwritten by this mojo; otherwise no dump happens and this mojo writes to
+     * {@link #catalogBaseDir} as usual.
+     *
+     * @since 2.3.0
      */
-    @Parameter(property = "cq.extensionDirectories", required = true)
-    List<File> extensionDirectories;
-
-    /**
-     * A set of artifactIdBases that are not extensions and should be excluded from the catalog
-     */
-    @Parameter(property = "cq.skipArtifactIdBases")
-    Set<String> skipArtifactIdBases;
+    @Parameter(property = "cq.extendClassPathCatalog", defaultValue = "false")
+    boolean extendClassPathCatalog;
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
-        final Path catalogPath = catalogBaseDir.toPath().resolve(CQ_CATALOG_DIR);
-        if (skipArtifactIdBases == null) {
-            skipArtifactIdBases = Collections.emptySet();
-        }
+        final Path catalogPath = catalogBaseDir.toPath().resolve(CqCatalog.CQ_CATALOG_DIR);
 
         final Map<String, Set<String>> schemesByKind = new LinkedHashMap<>();
         CqCatalog.kinds().forEach(kind -> schemesByKind.put(kind.name(), new TreeSet<>()));
 
-        final CqCatalog catalog = new CqCatalog();
-        extensionDirectories.stream()
-                .map(File::toPath)
-                .forEach(extDir -> {
+        final CqCatalog catalog = CqCatalog.findFirstFromClassPath();
+        if (extendClassPathCatalog) {
+            catalog.store(catalogBaseDir.toPath());
+        }
 
-                    /* Discover local components, such as Qute and add them to the catalog */
-                    CqUtils.findExtensionArtifactIdBases(extDir)
-                            .filter(artifactIdBase -> !skipArtifactIdBases.contains(artifactIdBase))
-                            .forEach(artifactIdBase -> {
-                                final Path schemaFile = extDir
-                                        .resolve(artifactIdBase)
-                                        .resolve("component/src/generated/resources/org/apache/camel/component/"
-                                                + artifactIdBase + "/" + artifactIdBase + ".json")
-                                        .toAbsolutePath().normalize();
-                                if (Files.isRegularFile(schemaFile)) {
-                                    try {
-                                        final String schema = new String(Files.readAllBytes(schemaFile),
-                                                StandardCharsets.UTF_8);
-                                        final String capBase = artifactIdBase.substring(0, 1).toUpperCase()
-                                                + artifactIdBase.substring(1);
-                                        getLog().debug("Adding an extra component " + artifactIdBase + " " +
-                                                "org.apache.camel.component." + artifactIdBase + "." + capBase + "Component " +
-                                                schema);
-                                        catalog.addComponent(artifactIdBase,
-                                                "org.apache.camel.component." + artifactIdBase + "." + capBase + "Component",
-                                                schema);
-                                    } catch (IOException e) {
-                                        throw new RuntimeException("Could not read " + schemaFile, e);
-                                    }
-                                }
-                            });
+        findExtensions()
+                .forEach(ext -> {
+                    final String artifactIdBase = ext.getArtifactIdBase();
+                    final Path schemaFile = ext
+                            .getExtensionDir()
+                            .resolve("component/src/generated/resources/org/apache/camel/component/"
+                                    + artifactIdBase + "/" + artifactIdBase + ".json")
+                            .toAbsolutePath().normalize();
+                    if (Files.isRegularFile(schemaFile)) {
+                        try {
+                            final String schema = new String(Files.readAllBytes(schemaFile),
+                                    StandardCharsets.UTF_8);
+                            final String capBase = artifactIdBase.substring(0, 1).toUpperCase()
+                                    + artifactIdBase.substring(1);
+                            getLog().debug("Adding an extra component " + artifactIdBase + " " +
+                                    "org.apache.camel.component." + artifactIdBase + "." + capBase + "Component " +
+                                    schema);
+                            catalog.addComponent(artifactIdBase,
+                                    "org.apache.camel.component." + artifactIdBase + "." + capBase + "Component",
+                                    schema);
+                        } catch (IOException e) {
+                            throw new RuntimeException("Could not read " + schemaFile, e);
+                        }
+                    }
                 });
 
-        extensionDirectories.stream()
-                .map(File::toPath)
-                .forEach(extDir -> {
-                    CqUtils.findExtensionArtifactIdBases(extDir)
-                            .filter(artifactIdBase -> !skipArtifactIdBases.contains(artifactIdBase))
-                            .forEach(artifactIdBase -> {
-                                final List<ArtifactModel<?>> models = catalog.filterModels(artifactIdBase)
-                                        .collect(Collectors.toList());
-                                final Path runtimePomXmlPath = extDir.resolve(artifactIdBase).resolve("runtime/pom.xml")
-                                        .toAbsolutePath().normalize();
-                                final CamelQuarkusExtension ext = CamelQuarkusExtension.read(runtimePomXmlPath);
-                                final boolean nativeSupported = ext.isNativeSupported();
-                                if (models.isEmpty()) {
-                                    final ArtifactModel<?> model;
-                                    final Kind extKind = ext.getKind();
-                                    if (extKind == Kind.component) {
-                                        model = new ComponentModel();
-                                    } else if (extKind == Kind.language) {
-                                        model = new LanguageModel();
-                                    } else if (extKind == Kind.dataformat) {
-                                        model = new DataFormatModel();
-                                    } else {
-                                        model = new OtherModel();
-                                    }
-                                    final String name = ext.getRuntimeArtifactId().replace("camel-quarkus-", "");
-                                    model.setName(name);
-                                    final String title = ext.getName().orElseThrow(() -> new RuntimeException(
-                                            "name is missing in " + ext.getRuntimePomXmlPath()));
-                                    model.setTitle(title);
-                                    model.setDescription(ext.getDescription().orElseThrow(() -> new RuntimeException(
-                                            "description is missing in " + ext.getRuntimePomXmlPath())));
-                                    model.setDeprecated(CqUtils.isDeprecated(title, models));
-                                    model.setLabel(ext.getLabel().orElse("quarkus"));
-                                    update(model, ext, nativeSupported);
-                                    serialize(catalogPath, model);
-                                    schemesByKind.get(model.getKind()).add(model.getName());
-                                } else {
-                                    for (ArtifactModel<?> model : models) {
-                                        update(model, ext, nativeSupported);
-                                        serialize(catalogPath, model);
-                                        schemesByKind.get(model.getKind()).add(model.getName());
-                                    }
-                                }
-                            });
+        findExtensions()
+                .forEach(extPath -> {
+                    final String artifactIdBase = extPath.getArtifactIdBase();
+                    final List<ArtifactModel<?>> models = catalog.filterModels(artifactIdBase)
+                            .collect(Collectors.toList());
+                    final Path runtimePomXmlPath = extPath
+                            .getExtensionDir().resolve("runtime/pom.xml")
+                            .toAbsolutePath().normalize();
+                    final CamelQuarkusExtension ext = CamelQuarkusExtension.read(runtimePomXmlPath);
+                    final boolean nativeSupported = ext.isNativeSupported();
+                    if (models.isEmpty()) {
+                        final ArtifactModel<?> model;
+                        final Kind extKind = ext.getKind();
+                        if (extKind == Kind.component) {
+                            model = new ComponentModel();
+                        } else if (extKind == Kind.language) {
+                            model = new LanguageModel();
+                        } else if (extKind == Kind.dataformat) {
+                            model = new DataFormatModel();
+                        } else {
+                            model = new OtherModel();
+                        }
+                        final String name = ext.getRuntimeArtifactId().replace("camel-quarkus-", "");
+                        model.setName(name);
+                        final String title = ext.getName().orElseThrow(() -> new RuntimeException(
+                                "name is missing in " + ext.getRuntimePomXmlPath()));
+                        model.setTitle(title);
+                        model.setDescription(ext.getDescription().orElseThrow(() -> new RuntimeException(
+                                "description is missing in " + ext.getRuntimePomXmlPath())));
+                        model.setDeprecated(CqUtils.isDeprecated(title, models));
+                        model.setLabel(ext.getLabel().orElse("quarkus"));
+                        update(model, ext, nativeSupported);
+                        CqCatalog.serialize(catalogPath, model);
+                        schemesByKind.get(model.getKind()).add(model.getName());
+                    } else {
+                        for (ArtifactModel<?> model : models) {
+                            update(model, ext, nativeSupported);
+                            CqCatalog.serialize(catalogPath, model);
+                            schemesByKind.get(model.getKind()).add(model.getName());
+                        }
+                    }
                 });
 
         CqCatalog.kinds().forEach(kind -> {
@@ -174,39 +160,6 @@ public class PrepareCatalogQuarkusMojo extends AbstractMojo {
 
     }
 
-    private void serialize(final Path catalogPath, ArtifactModel<?> model) {
-        final Path out = catalogPath.resolve(model.getKind() + "s")
-                .resolve(model.getName() + ".json");
-        try {
-            Files.createDirectories(out.getParent());
-        } catch (IOException e) {
-            throw new RuntimeException("Could not create " + out.getParent(), e);
-        }
-        String rawJson;
-        switch (Kind.valueOf(model.getKind())) {
-        case component:
-            rawJson = JsonMapper.createParameterJsonSchema((ComponentModel) model);
-            break;
-        case language:
-            rawJson = JsonMapper.createParameterJsonSchema((LanguageModel) model);
-            break;
-        case dataformat:
-            rawJson = JsonMapper.createParameterJsonSchema((DataFormatModel) model);
-            break;
-        case other:
-            rawJson = JsonMapper.createJsonSchema((OtherModel) model);
-            break;
-        default:
-            throw new IllegalStateException("Cannot serialize kind " + model.getKind());
-        }
-
-        try {
-            Files.write(out, rawJson.getBytes(StandardCharsets.UTF_8));
-        } catch (IOException e) {
-            throw new RuntimeException("Could not write to " + out);
-        }
-    }
-
     private static void update(ArtifactModel<?> model, CamelQuarkusExtension ext, boolean nativeSupported) {
         final String firstVersion = ext.getJvmSince()
                 .orElseThrow(() -> new RuntimeException(