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 <claus.ib...@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; + } + +}