This is an automated email from the ASF dual-hosted git repository.
bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-maven.git
The following commit(s) were added to refs/heads/main by this push:
new b3dafe9 NIFI-11217 Fix building external NARs with transitive
dependencies... (#29)
b3dafe9 is described below
commit b3dafe979030c1b797ea2535c8cbbc44c22e9dfc
Author: Kevin Doran <[email protected]>
AuthorDate: Wed Mar 8 12:56:34 2023 -0500
NIFI-11217 Fix building external NARs with transitive dependencies... (#29)
* NIFI-11217 Fix building external NARs with transitive dependencies marked
as provided
* Add unit test for ExtensionClassLoaderFactory
* Bump maven-surefire-plugin version
* Clean up
---
pom.xml | 21 ++-
src/main/java/org/apache/nifi/NarMojo.java | 19 +-
.../extraction/ExtensionClassLoaderFactory.java | 27 ++-
.../ExtensionClassLoaderFactoryTest.java | 204 +++++++++++++++++++++
4 files changed, 255 insertions(+), 16 deletions(-)
diff --git a/pom.xml b/pom.xml
index a578b83..0654543 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,7 +109,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.18</version>
+ <version>3.0.0-M8</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<argLine>-Xmx1G</argLine>
@@ -340,6 +340,25 @@
<scope>provided</scope>
<version>3.3</version>
</dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <version>5.9.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.12.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>3.12.4</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<profiles>
<profile>
diff --git a/src/main/java/org/apache/nifi/NarMojo.java
b/src/main/java/org/apache/nifi/NarMojo.java
index e1cb252..31a16dd 100644
--- a/src/main/java/org/apache/nifi/NarMojo.java
+++ b/src/main/java/org/apache/nifi/NarMojo.java
@@ -850,15 +850,16 @@ public class NarMojo extends AbstractMojo {
private ExtensionClassLoaderFactory createClassLoaderFactory() {
return new ExtensionClassLoaderFactory.Builder()
- .artifactResolver(resolver)
- .dependencyGraphBuilder(dependencyGraphBuilder)
- .localRepository(local)
- .log(getLog())
- .project(project)
- .projectBuilder(projectBuilder)
- .repositorySession(repoSession)
- .artifactHandlerManager(artifactHandlerManager)
- .build();
+ .artifactResolver(resolver)
+ .dependencyGraphBuilder(dependencyGraphBuilder)
+ .localRepository(local)
+ .remoteRepositories(remoteRepos)
+ .log(getLog())
+ .project(project)
+ .projectBuilder(projectBuilder)
+ .repositorySession(repoSession)
+ .artifactHandlerManager(artifactHandlerManager)
+ .build();
}
diff --git
a/src/main/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactory.java
b/src/main/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactory.java
index 57bbc7d..c5e4888 100644
---
a/src/main/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactory.java
+++
b/src/main/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactory.java
@@ -51,6 +51,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Supplier;
public class ExtensionClassLoaderFactory {
@@ -67,6 +68,7 @@ public class ExtensionClassLoaderFactory {
private final RepositorySystemSession repoSession;
private final ProjectBuilder projectBuilder;
private final ArtifactRepository localRepo;
+ private final List<ArtifactRepository> remoteRepos;
private final DependencyGraphBuilder dependencyGraphBuilder;
private final ArtifactResolver artifactResolver;
private final ArtifactHandlerManager artifactHandlerManager;
@@ -77,6 +79,7 @@ public class ExtensionClassLoaderFactory {
this.repoSession = builder.repositorySession;
this.projectBuilder = builder.projectBuilder;
this.localRepo = builder.localRepo;
+ this.remoteRepos = new ArrayList<>(builder.remoteRepos);
this.dependencyGraphBuilder = builder.dependencyGraphBuilder;
this.artifactResolver = builder.artifactResolver;
this.artifactHandlerManager = builder.artifactHandlerManager;
@@ -149,8 +152,7 @@ public class ExtensionClassLoaderFactory {
final ProjectBuildingResult narResult =
projectBuilder.build(narArtifact, narRequest);
- final Set<Artifact> narDependencies = new TreeSet<>();
- gatherArtifacts(narResult.getProject(), narDependencies);
+ final Set<Artifact> narDependencies =
gatherArtifacts(narResult.getProject(), TreeSet::new);
narDependencies.remove(narArtifact);
narDependencies.remove(project.getArtifact());
@@ -177,10 +179,9 @@ public class ExtensionClassLoaderFactory {
projectRequest.setLocalRepository(localRepo);
for (final Artifact artifact : artifacts) {
- final Set<Artifact> artifactDependencies = new HashSet<>();
try {
final ProjectBuildingResult projectResult =
projectBuilder.build(artifact, projectRequest);
- gatherArtifacts(projectResult.getProject(),
artifactDependencies);
+ final Set<Artifact> artifactDependencies =
gatherArtifacts(projectResult.getProject(), HashSet::new);
getLog().debug("For Artifact " + artifact + ", found the
following dependencies:");
artifactDependencies.forEach(dep ->
getLog().debug(dep.toString()));
@@ -213,6 +214,7 @@ public class ExtensionClassLoaderFactory {
final ArtifactResolutionRequest request = new
ArtifactResolutionRequest();
request.setLocalRepository(localRepo);
+ request.setRemoteRepositories(remoteRepos);
request.setArtifact(artifact);
final ArtifactResolutionResult result =
artifactResolver.resolve(request);
@@ -266,7 +268,8 @@ public class ExtensionClassLoaderFactory {
return createClassLoader(providedArtifacts, null, null);
}
- private ExtensionClassLoader createClassLoader(final Set<Artifact>
artifacts, final ExtensionClassLoader parent, final Artifact narArtifact)
throws MojoExecutionException {
+ /* package visible for testing reasons */
+ ExtensionClassLoader createClassLoader(final Set<Artifact> artifacts,
final ExtensionClassLoader parent, final Artifact narArtifact) throws
MojoExecutionException {
final Set<URL> urls = new HashSet<>();
for (final Artifact artifact : artifacts) {
final Set<URL> artifactUrls = toURLs(artifact);
@@ -284,7 +287,8 @@ public class ExtensionClassLoaderFactory {
}
- private void gatherArtifacts(final MavenProject mavenProject, final
Set<Artifact> artifacts) throws MojoExecutionException {
+ private Set<Artifact> gatherArtifacts(final MavenProject mavenProject,
final Supplier<Set<Artifact>> setSupplier) throws MojoExecutionException {
+ final Set<Artifact> artifacts = setSupplier.get();
final DependencyNodeVisitor nodeVisitor = new DependencyNodeVisitor() {
@Override
public boolean visit(final DependencyNode dependencyNode) {
@@ -312,6 +316,7 @@ public class ExtensionClassLoaderFactory {
} catch (DependencyGraphBuilderException e) {
throw new MojoExecutionException("Failed to build dependency
tree", e);
}
+ return artifacts;
}
private Set<URL> toURLs(final Artifact artifact) throws
MojoExecutionException {
@@ -323,6 +328,7 @@ public class ExtensionClassLoaderFactory {
final ArtifactResolutionRequest request = new
ArtifactResolutionRequest();
request.setLocalRepository(localRepo);
+ request.setRemoteRepositories(remoteRepos);
request.setArtifact(artifact);
final ArtifactResolutionResult result =
artifactResolver.resolve(request);
@@ -348,12 +354,16 @@ public class ExtensionClassLoaderFactory {
return urls;
}
+ public static Builder builder() {
+ return new Builder();
+ }
public static class Builder {
private Log log;
private MavenProject project;
private ArtifactRepository localRepo;
+ private List<ArtifactRepository> remoteRepos;
private DependencyGraphBuilder dependencyGraphBuilder;
private ArtifactResolver artifactResolver;
private ProjectBuilder projectBuilder;
@@ -380,6 +390,11 @@ public class ExtensionClassLoaderFactory {
return this;
}
+ public Builder remoteRepositories(final List<ArtifactRepository>
remoteRepos) {
+ this.remoteRepos = remoteRepos;
+ return this;
+ }
+
public Builder dependencyGraphBuilder(final DependencyGraphBuilder
dependencyGraphBuilder) {
this.dependencyGraphBuilder = dependencyGraphBuilder;
return this;
diff --git
a/src/test/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactoryTest.java
b/src/test/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactoryTest.java
new file mode 100644
index 0000000..c6e6cf0
--- /dev/null
+++
b/src/test/java/org/apache/nifi/extension/definition/extraction/ExtensionClassLoaderFactoryTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.nifi.extension.definition.extraction;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
+import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
+import org.eclipse.aether.RepositorySystemSession;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class ExtensionClassLoaderFactoryTest {
+
+ @Mock private Log log;
+ @Mock private ArtifactResolver artifactResolver;
+ @Mock private ArtifactRepository localRepository;
+ @Mock private ArtifactRepository remoteRepository;
+ @Mock private ArtifactHandlerManager artifactHandlerManager;
+ @Mock private DependencyGraphBuilder dependencyGraphBuilder;
+ @Mock private MavenProject project;
+ @Mock private ProjectBuilder projectBuilder;
+ @Mock private RepositorySystemSession repositorySession;
+ private Artifact artifact1;
+ private Artifact artifact2;
+ private Artifact artifact3;
+
+ // Test Subject
+ private ExtensionClassLoaderFactory factory;
+
+ @BeforeEach
+ void setUp() {
+ artifact1 = projectArtifact();
+ artifact2 = localRepositoryDependencyArtifact();
+ artifact3 = remoteRepositoryDependencyArtifact();
+
+ when(artifactResolver.resolve(any(ArtifactResolutionRequest.class)))
+ .thenAnswer(args -> resolved(args.getArgument(0,
ArtifactResolutionRequest.class).getArtifact()));
+
+ factory = ExtensionClassLoaderFactory
+ .builder()
+ .log(log)
+ .project(project)
+ .projectBuilder(projectBuilder)
+ .dependencyGraphBuilder(dependencyGraphBuilder)
+ .artifactHandlerManager(artifactHandlerManager)
+ .artifactResolver(artifactResolver)
+ .localRepository(localRepository)
+
.remoteRepositories(Collections.singletonList(remoteRepository))
+ .repositorySession(repositorySession)
+ .build();
+ }
+
+ @Test
+ void createClassLoaderTest() throws Exception {
+ Set<Artifact> dependencyArtifacts = new TreeSet<>();
+ dependencyArtifacts.add(localRepositoryDependencyArtifact());
+ dependencyArtifacts.add(remoteRepositoryDependencyArtifact());
+
+ ExtensionClassLoader classLoader =
factory.createClassLoader(dependencyArtifacts, null, artifact1);
+
+ String[] expectedURLs = new String[]{
+ "/path/to/service-api-nar",
+ "/path/to/service-nar"
+ };
+ assertEquals(expectedURLs.length, classLoader.getURLs().length);
+ List<String> expectedUrlsList = Arrays.asList(expectedURLs);
+ List<String> actualUrlsList =
Arrays.stream(classLoader.getURLs()).map(URL::getFile).collect(Collectors.toList());
+ assertTrue(expectedUrlsList.containsAll(actualUrlsList));
+
+ InOrder inOrder = inOrder(artifactResolver);
+ for (ArtifactResolutionRequest req :
getExpectedArtifactResolutionRequests()) {
+ inOrder.verify(artifactResolver).resolve(argThat(arg ->
+
req.getArtifact().getArtifactId().equals(arg.getArtifact().getArtifactId())
+ && req.getLocalRepository() == arg.getLocalRepository()
+ &&
req.getRemoteRepositories().equals(arg.getRemoteRepositories())
+ ));
+ }
+ verifyNoMoreInteractions(artifactResolver);
+ }
+
+ private List<ArtifactResolutionRequest>
getExpectedArtifactResolutionRequests() {
+ ArtifactResolutionRequest request1 = new ArtifactResolutionRequest();
+ request1.setArtifact(artifact2);
+ request1.setLocalRepository(localRepository);
+
request1.setRemoteRepositories(Collections.singletonList(remoteRepository));
+
+ ArtifactResolutionRequest request2 = new ArtifactResolutionRequest();
+ request2.setArtifact(artifact3);
+ request2.setLocalRepository(localRepository);
+
request2.setRemoteRepositories(Collections.singletonList(remoteRepository));
+
+ List<ArtifactResolutionRequest> resolutionRequests = new ArrayList<>();
+ resolutionRequests.add(request1);
+ resolutionRequests.add(request2);
+ return resolutionRequests;
+ }
+
+ private Artifact projectArtifact() {
+ Artifact artifact = new DefaultArtifact(
+ "org.apache.nifi",
+ "processor-nar",
+ "1.0.0",
+ "compile",
+ "nar",
+ "arbitrary",
+ mock(ArtifactHandler.class)
+ );
+ artifact.setFile(new File("/path/to/" + artifact.getArtifactId()));
+ return artifact;
+ }
+
+ private Artifact localRepositoryDependencyArtifact() {
+ Artifact artifact = new DefaultArtifact(
+ "org.apache.nifi",
+ "service-api-nar",
+ "1.0.0",
+ "compile",
+ "nar",
+ "arbitrary",
+ mock(ArtifactHandler.class)
+ );
+ artifact.setFile(null);
+ artifact.setRepository(localRepository);
+ return artifact;
+ }
+
+ private Artifact remoteRepositoryDependencyArtifact() {
+ Artifact artifact = new DefaultArtifact(
+ "org.apache.nifi",
+ "service-nar",
+ "1.0.0",
+ "provided",
+ "nar",
+ "arbitrary",
+ mock(ArtifactHandler.class)
+ );
+ artifact.setFile(null);
+ artifact.setRepository(remoteRepository);
+ return artifact;
+ }
+
+ private ArtifactResolutionResult resolved(Artifact artifact) {
+ Artifact resolvedArtifact = new DefaultArtifact(
+ artifact.getGroupId(),
+ artifact.getArtifactId(),
+ artifact.getVersion(),
+ artifact.getScope(),
+ artifact.getType(),
+ artifact.getClassifier(),
+ artifact.getArtifactHandler()
+ );
+ resolvedArtifact.setFile(new File("/path/to/" +
artifact.getArtifactId()));
+ ArtifactResolutionResult result = new ArtifactResolutionResult();
+ result.setArtifacts(Collections.singleton(resolvedArtifact));
+ return result;
+ }
+}
\ No newline at end of file