You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/04/01 13:33:15 UTC

[camel] 01/02: CAMEL-16419: camel-maven-plugin - Add prepare-fatjar goal for better support of packaging Camel JARs to a fat-jar

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

davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 64c5464181f716a35f868c98bab991801c3bc6d9
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Apr 1 15:22:12 2021 +0200

    CAMEL-16419: camel-maven-plugin - Add prepare-fatjar goal for better support of packaging Camel JARs to a fat-jar
---
 .../impl/converter/BaseTypeConverterRegistry.java  |  22 +-
 .../org/apache/camel/maven/DynamicClassLoader.java |  40 ++++
 .../org/apache/camel/maven/PrepareFatJarMojo.java  | 262 +++++++++++++++++++++
 3 files changed, 317 insertions(+), 7 deletions(-)

diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java b/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
index 15b7c26..3c0396b 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/converter/BaseTypeConverterRegistry.java
@@ -51,6 +51,8 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class BaseTypeConverterRegistry extends CoreTypeConverterRegistry {
 
+    private static final String META_INF_SERVICES_UBER_TYPE_CONVERTER_LOADER
+            = "META-INF/services/org/apache/camel/UberTypeConverterLoader";
     public static final String META_INF_SERVICES_TYPE_CONVERTER_LOADER
             = "META-INF/services/org/apache/camel/TypeConverterLoader";
     public static final String META_INF_SERVICES_FALLBACK_TYPE_CONVERTER
@@ -171,14 +173,21 @@ public abstract class BaseTypeConverterRegistry extends CoreTypeConverterRegistr
 
     /**
      * Finds the type converter loader classes from the classpath looking for text files on the classpath at the
-     * {@link #META_INF_SERVICES_TYPE_CONVERTER_LOADER} location.
+     * {@link #META_INF_SERVICES_UBER_TYPE_CONVERTER_LOADER} and {@link #META_INF_SERVICES_TYPE_CONVERTER_LOADER}
+     * locations.
      */
     protected Collection<String> findTypeConverterLoaderClasses() throws IOException {
-        Set<String> loaders = new LinkedHashSet<>();
-        Collection<URL> loaderResources = getLoaderUrls();
+        Collection<String> loaders = new LinkedHashSet<>();
+        findTypeConverterLoaderClasses(loaders, META_INF_SERVICES_UBER_TYPE_CONVERTER_LOADER);
+        findTypeConverterLoaderClasses(loaders, META_INF_SERVICES_TYPE_CONVERTER_LOADER);
+        return loaders;
+    }
+
+    protected void findTypeConverterLoaderClasses(Collection<String> loaders, String basePath) throws IOException {
+        Collection<URL> loaderResources = getLoaderUrls(basePath);
         for (URL url : loaderResources) {
             LOG.debug("Loading file {} to retrieve list of type converters, from url: {}",
-                    META_INF_SERVICES_TYPE_CONVERTER_LOADER, url);
+                    basePath, url);
             BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8));
             String line;
             do {
@@ -189,13 +198,12 @@ public abstract class BaseTypeConverterRegistry extends CoreTypeConverterRegistr
             } while (line != null);
             IOHelper.close(reader);
         }
-        return loaders;
     }
 
-    protected Collection<URL> getLoaderUrls() throws IOException {
+    protected Collection<URL> getLoaderUrls(String basePath) throws IOException {
         List<URL> loaderResources = new ArrayList<>();
         for (ClassLoader classLoader : resolver.getClassLoaders()) {
-            Enumeration<URL> resources = classLoader.getResources(META_INF_SERVICES_TYPE_CONVERTER_LOADER);
+            Enumeration<URL> resources = classLoader.getResources(basePath);
             while (resources.hasMoreElements()) {
                 URL url = resources.nextElement();
                 loaderResources.add(url);
diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DynamicClassLoader.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DynamicClassLoader.java
new file mode 100644
index 0000000..5d9d669
--- /dev/null
+++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/DynamicClassLoader.java
@@ -0,0 +1,40 @@
+/*
+ * 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.net.URL;
+import java.net.URLClassLoader;
+import java.util.Iterator;
+import java.util.List;
+
+class DynamicClassLoader extends URLClassLoader {
+
+    public DynamicClassLoader(URL[] urls, ClassLoader parent) {
+        super(urls, parent);
+    }
+
+    public static DynamicClassLoader createDynamicClassLoaderFromUrls(List<URL> classpathElements) {
+        final URL[] urls = new URL[classpathElements.size()];
+        int i = 0;
+        for (Iterator<URL> it = classpathElements.iterator(); it.hasNext(); i++) {
+            urls[i] = it.next();
+        }
+        // no parent classloader as we only want to load from the given URLs
+        return new DynamicClassLoader(urls, null);
+    }
+
+}
diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/PrepareFatJarMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/PrepareFatJarMojo.java
new file mode 100644
index 0000000..8a3d43e
--- /dev/null
+++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/PrepareFatJarMojo.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.factory.ArtifactFactory;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Exclusion;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+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;
+import org.apache.maven.project.MavenProject;
+
+@Mojo(name = "prepare-fatjar", threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE,
+      defaultPhase = LifecyclePhase.PREPARE_PACKAGE)
+public class PrepareFatJarMojo extends AbstractMojo {
+
+    private static final String GENERATED_MSG = "Generated by camel build tools - do NOT edit this file!";
+    private static final String NL = "\n";
+
+    private static final String META_INF_SERVICES_TYPE_CONVERTER_LOADER
+            = "META-INF/services/org/apache/camel/TypeConverterLoader";
+
+    private static final String META_INF_SERVICES_UBER_TYPE_CONVERTER_LOADER
+            = "META-INF/services/org/apache/camel/UberTypeConverterLoader";
+
+    private DynamicClassLoader projectClassLoader;
+
+    @Parameter(property = "project", required = true, readonly = true)
+    private MavenProject project;
+    @Parameter(defaultValue = "${project.build.outputDirectory}")
+    private File classesDirectory;
+    @Component
+    private ArtifactFactory artifactFactory;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        Collection<String> loaders = findTypeConverterLoaderClasses();
+        if (loaders.isEmpty()) {
+            return;
+        }
+
+        getLog().info("Found " + loaders.size() + " Camel type converter loaders from project classpath");
+
+        // prepare output to generate
+        StringBuilder sb = new StringBuilder();
+        sb.append("# ");
+        sb.append(GENERATED_MSG);
+        sb.append(NL);
+        sb.append(String.join(NL, loaders));
+        sb.append(NL);
+
+        String data = sb.toString();
+
+        File file = new File(classesDirectory, META_INF_SERVICES_UBER_TYPE_CONVERTER_LOADER);
+        try {
+            writeFile(file, data);
+        } catch (IOException e) {
+            throw new MojoFailureException("Error updating " + file, e);
+        }
+    }
+
+    private void writeFile(File file, String data) throws IOException {
+        Path path = file.toPath();
+        Files.createDirectories(path.getParent());
+        Files.write(path, data.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.CREATE,
+                StandardOpenOption.TRUNCATE_EXISTING);
+    }
+
+    /**
+     * Finds the type converter loader classes from the classpath looking for text files on the classpath at the
+     * {@link #META_INF_SERVICES_TYPE_CONVERTER_LOADER} location.
+     */
+    protected Collection<String> findTypeConverterLoaderClasses() {
+        Set<String> loaders = new LinkedHashSet<>();
+
+        try {
+            Enumeration<URL> loaderResources = getProjectClassLoader().getResources(META_INF_SERVICES_TYPE_CONVERTER_LOADER);
+            while (loaderResources.hasMoreElements()) {
+                URL url = loaderResources.nextElement();
+                getLog().debug("Loading file " + META_INF_SERVICES_TYPE_CONVERTER_LOADER
+                               + " to retrieve list of type converters, from url: " + url);
+                BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8));
+                String line;
+                do {
+                    line = reader.readLine();
+                    if (line != null && !line.startsWith("#") && !line.isEmpty()) {
+                        loaders.add(line);
+                    }
+                } while (line != null);
+                try {
+                    reader.close();
+                } catch (Exception e) {
+                    // ignore
+                }
+            }
+        } catch (Exception e) {
+            getLog().warn("Error finding type converters due to " + e.getMessage());
+        }
+
+        return loaders;
+    }
+
+    protected final DynamicClassLoader getProjectClassLoader() throws MojoExecutionException {
+        if (projectClassLoader == null) {
+            List<URL> urls = new ArrayList<>();
+            // need to include project compile dependencies (code similar to camel-maven-plugin)
+            addRelevantProjectDependenciesToClasspath(urls, false);
+            projectClassLoader = DynamicClassLoader.createDynamicClassLoaderFromUrls(urls);
+        }
+        return projectClassLoader;
+    }
+
+    /**
+     * Add any relevant project dependencies to the classpath. Takes includeProjectDependencies into consideration.
+     *
+     * @param path classpath of {@link URL} objects
+     */
+    private void addRelevantProjectDependenciesToClasspath(List<URL> path, boolean testClasspathOnly)
+            throws MojoExecutionException {
+        try {
+            getLog().debug("Project Dependencies will be included.");
+
+            if (testClasspathOnly) {
+                URL testClasses = new File(project.getBuild().getTestOutputDirectory()).toURI().toURL();
+                getLog().debug("Adding to classpath : " + testClasses);
+                path.add(testClasses);
+            } else {
+                URL mainClasses = new File(project.getBuild().getOutputDirectory()).toURI().toURL();
+                getLog().debug("Adding to classpath : " + mainClasses);
+                path.add(mainClasses);
+            }
+
+            Set<Artifact> dependencies = project.getArtifacts();
+
+            // system scope dependencies are not returned by maven 2.0. See
+            // MEXEC-17
+            dependencies.addAll(getAllNonTestScopedDependencies());
+
+            Iterator<Artifact> iter = dependencies.iterator();
+            while (iter.hasNext()) {
+                Artifact classPathElement = iter.next();
+                getLog().debug("Adding project dependency artifact: " + classPathElement.getArtifactId()
+                               + " to classpath");
+                File file = classPathElement.getFile();
+                if (file != null) {
+                    path.add(file.toURI().toURL());
+                }
+            }
+
+        } catch (MalformedURLException e) {
+            throw new MojoExecutionException("Error during setting up classpath", e);
+        }
+    }
+
+    private Collection<Artifact> getAllNonTestScopedDependencies() throws MojoExecutionException {
+        List<Artifact> answer = new ArrayList<>();
+
+        for (Artifact artifact : getAllDependencies()) {
+
+            // do not add test artifacts
+            if (!artifact.getScope().equals(Artifact.SCOPE_TEST)) {
+                answer.add(artifact);
+            }
+        }
+        return answer;
+    }
+
+    // generic method to retrieve all the transitive dependencies
+    private Collection<Artifact> getAllDependencies() throws MojoExecutionException {
+        List<Artifact> artifacts = new ArrayList<>();
+
+        for (Iterator<?> dependencies = project.getDependencies().iterator(); dependencies.hasNext();) {
+            Dependency dependency = (Dependency) dependencies.next();
+
+            String groupId = dependency.getGroupId();
+            String artifactId = dependency.getArtifactId();
+
+            VersionRange versionRange;
+            try {
+                versionRange = VersionRange.createFromVersionSpec(dependency.getVersion());
+            } catch (InvalidVersionSpecificationException e) {
+                throw new MojoExecutionException("unable to parse version", e);
+            }
+
+            String type = dependency.getType();
+            if (type == null) {
+                type = "jar";
+            }
+            String classifier = dependency.getClassifier();
+            boolean optional = dependency.isOptional();
+            String scope = dependency.getScope();
+            if (scope == null) {
+                scope = Artifact.SCOPE_COMPILE;
+            }
+
+            if (this.artifactFactory != null) {
+                Artifact art = this.artifactFactory.createDependencyArtifact(groupId, artifactId, versionRange,
+                        type, classifier, scope, null, optional);
+
+                if (scope.equalsIgnoreCase(Artifact.SCOPE_SYSTEM)) {
+                    art.setFile(new File(dependency.getSystemPath()));
+                }
+
+                List<String> exclusions = new ArrayList<>();
+                for (Exclusion exclusion : dependency.getExclusions()) {
+                    exclusions.add(exclusion.getGroupId() + ":" + exclusion.getArtifactId());
+                }
+
+                ArtifactFilter newFilter = new ExcludesArtifactFilter(exclusions);
+
+                art.setDependencyFilter(newFilter);
+
+                artifacts.add(art);
+            }
+        }
+
+        return artifacts;
+    }
+
+}