2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
19 package org.apache.karaf.tooling.utils;
21 import org.apache.maven.RepositoryUtils;
22 import org.apache.maven.plugin.MojoExecutionException;
23 import org.apache.maven.plugin.MojoFailureException;
24 import org.apache.maven.plugin.logging.Log;
25 import org.apache.maven.project.MavenProject;
26 import org.apache.maven.project.ProjectBuildingRequest;
27 import org.eclipse.aether.DefaultRepositorySystemSession;
28 import org.eclipse.aether.RepositorySystem;
29 import org.eclipse.aether.RepositorySystemSession;
30 import org.eclipse.aether.artifact.Artifact;
31 import org.eclipse.aether.artifact.DefaultArtifact;
32 import org.eclipse.aether.collection.*;
33 import org.eclipse.aether.graph.Dependency;
34 import org.eclipse.aether.graph.DependencyNode;
35 import org.eclipse.aether.repository.RemoteRepository;
36 import org.eclipse.aether.resolution.ArtifactRequest;
37 import org.eclipse.aether.resolution.ArtifactResolutionException;
38 import org.eclipse.aether.resolution.ArtifactResult;
39 import org.eclipse.aether.util.graph.selector.AndDependencySelector;
40 import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
41 import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
42 import org.eclipse.aether.util.graph.transformer.*;
45 import java.lang.reflect.InvocationTargetException;
46 import java.lang.reflect.Method;
49 import static java.lang.String.*;
50 import static org.apache.commons.lang.reflect.MethodUtils.invokeMethod;
51 import static org.apache.karaf.deployer.kar.KarArtifactInstaller.FEATURE_CLASSIFIER;
54 * <p>{@link DependencyHelper} for accessing Eclipse Aether system used in Maven 3.1+. It uses reflection to access
55 * these methods of {@code maven-core} APIs which directly references Eclipse Aether classes.</p>
57 * <p>When {@code karaf-maven-plugin} switches to {@code maven-core:3.1.0+}, reflection should be use for Sonatype
58 * Aether in {@link Dependency30Helper} and this class will use Maven API directly.</p>
60 public class Dependency31Helper implements DependencyHelper {
63 * The entry point to Aether, i.e. the component doing all the work.
65 private final RepositorySystem repositorySystem;
68 * The current repository/network configuration of Maven.
70 private final RepositorySystemSession repositorySystemSession;
73 * The project's remote repositories to use for the resolution of project dependencies.
75 private final List<RemoteRepository> projectRepositories;
77 // dependencies we are interested in
78 protected Set<LocalDependency> localDependencies;
79 // log of what happened during search
80 protected String treeListing;
82 @SuppressWarnings("unchecked")
83 public Dependency31Helper(List<?> repositories, Object session, RepositorySystem repositorySystem) {
84 this.projectRepositories = (List<RemoteRepository>) repositories;
85 this.repositorySystemSession = (RepositorySystemSession) session;
86 this.repositorySystem = repositorySystem;
89 public void setRepositorySession(final ProjectBuildingRequest request) throws MojoExecutionException {
91 invokeMethod(request, "setRepositorySession", repositorySystemSession);
92 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
93 throw new MojoExecutionException("Cannot set repository session on project building request", e);
98 public Set<LocalDependency> getLocalDependencies() {
99 return localDependencies;
103 public String getTreeListing() {
108 public void getDependencies(MavenProject project, boolean useTransitiveDependencies) throws MojoExecutionException {
109 DependencyNode rootNode = getDependencyTree(toArtifact(project.getArtifact()));
111 Scanner scanner = new Scanner();
112 scanner.scan(rootNode, useTransitiveDependencies);
113 localDependencies = scanner.localDependencies;
114 treeListing = scanner.getLog();
117 private DependencyNode getDependencyTree(Artifact artifact) throws MojoExecutionException {
119 CollectRequest collectRequest = new CollectRequest(new Dependency(artifact, "compile"), null, projectRepositories);
120 DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(repositorySystemSession);
121 session.setDependencySelector(new AndDependencySelector(new OptionalDependencySelector(),
122 new ScopeDependencySelector1(),
123 new ExclusionDependencySelector()));
124 // between aether-util 0.9.0.M1 and M2, JavaEffectiveScopeCalculator was removed
125 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=397241
126 DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer(new ConflictMarker(),
127 new ConflictResolver(new NearestVersionSelector(), new JavaScopeSelector(), new SimpleOptionalitySelector(), new JavaScopeDeriver()),
128 new JavaDependencyContextRefiner());
129 session.setDependencyGraphTransformer(transformer);
130 CollectResult result = repositorySystem.collectDependencies(session, collectRequest);
131 return result.getRoot();
132 } catch (DependencyCollectionException e) {
133 throw new MojoExecutionException("Cannot build project dependency tree", e);
138 * Aether's ScopeDependencySelector appears to always exclude the configured scopes (test and provided) and there is no way to configure it to
139 * accept the top level provided scope dependencies. We need this 3 layers cake since Aether never actually uses the top level selector you give it,
140 * it always starts by getting the child to apply to the project's dependencies.
142 private static class ScopeDependencySelector1 implements DependencySelector {
144 private DependencySelector child = new ScopeDependencySelector2();
146 public boolean selectDependency(Dependency dependency) {
147 throw new IllegalStateException("This does not appear to be called");
150 public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
156 public static class ScopeDependencySelector2 implements DependencySelector {
158 private DependencySelector child = new ScopeDependencySelector3();
160 public boolean selectDependency(Dependency dependency) {
161 String scope = dependency.getScope();
162 return !"test".equals(scope) && !"runtime".equals(scope);
165 public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
171 private static class ScopeDependencySelector3 implements DependencySelector {
173 public boolean selectDependency(Dependency dependency) {
174 String scope = dependency.getScope();
175 return !"test".equals(scope) && !"provided".equals(scope) && !"runtime".equals(scope);
178 public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
184 private static class Scanner {
186 private static enum Accept {
188 PROVIDED(true, false),
191 private final boolean more;
192 private final boolean local;
194 private Accept(boolean more, boolean local) {
199 public boolean isContinue() {
203 public boolean isLocal() {
208 // all the dependencies needed, with provided dependencies removed
209 private final Set<LocalDependency> localDependencies = new LinkedHashSet<>();
211 // dependencies from ancestor, to be removed from localDependencies
212 private final Set<Artifact> dependencies = new LinkedHashSet<>();
214 private final StringBuilder log = new StringBuilder();
216 public void scan(DependencyNode rootNode, boolean useTransitiveDependencies) throws MojoExecutionException {
217 for (DependencyNode child : rootNode.getChildren()) {
218 scan(rootNode, child, Accept.ACCEPT, useTransitiveDependencies, false, "");
220 if (useTransitiveDependencies) {
221 localDependencies.removeAll(dependencies);
225 private void scan(DependencyNode parentNode, DependencyNode dependencyNode, Accept parentAccept, boolean useTransitiveDependencies, boolean isFromFeature, String indent) throws MojoExecutionException {
226 Accept accept = accept(dependencyNode, parentAccept);
227 if (accept.isLocal()) {
229 if (!isFeature(dependencyNode)) {
230 log.append(indent).append("from feature:").append(dependencyNode).append("\n");
231 dependencies.add(dependencyNode.getDependency().getArtifact());
233 log.append(indent).append("is feature:").append(dependencyNode).append("\n");
236 log.append(indent).append("local:").append(dependencyNode).append("\n");
237 if (localDependencies.contains(dependencyNode.getDependency().getArtifact())) {
238 log.append(indent).append("already in feature, returning:").append(dependencyNode).append("\n");
241 // TODO resolve scope conflicts
242 localDependencies.add(new LocalDependency(dependencyNode.getDependency().getScope(), dependencyNode.getDependency().getArtifact(), parentNode.getDependency().getArtifact()));
243 if (isFeature(dependencyNode) || !useTransitiveDependencies) {
244 isFromFeature = true;
247 if (useTransitiveDependencies && accept.isContinue()) {
248 List<DependencyNode> children = dependencyNode.getChildren();
249 for (DependencyNode child : children) {
250 scan(dependencyNode, child, accept, useTransitiveDependencies, isFromFeature, indent + " ");
256 public String getLog() {
257 return log.toString();
260 private Accept accept(DependencyNode dependency, Accept previous) {
261 String scope = dependency.getDependency().getScope();
262 if (scope == null || "runtime".equalsIgnoreCase(scope) || "compile".equalsIgnoreCase(scope)) {
265 if ("provided".equalsIgnoreCase(scope)) {
266 return Accept.PROVIDED;
274 public static boolean isFeature(DependencyNode dependencyNode) {
275 return isFeature(dependencyNode.getDependency().getArtifact());
278 public static boolean isFeature(Artifact artifact) {
279 return artifact.getExtension().equals("kar") || FEATURE_CLASSIFIER.equals(artifact.getClassifier());
283 public boolean isArtifactAFeature(Object artifact) {
284 return Dependency31Helper.isFeature((Artifact) artifact);
288 public String getBaseVersion(Object artifact) {
289 return ((Artifact) artifact).getBaseVersion();
293 public String getGroupId(Object artifact) {
294 return ((Artifact) artifact).getGroupId();
298 public String getArtifactId(Object artifact) {
299 return ((Artifact) artifact).getArtifactId();
303 public String getClassifier(Object artifact) {
304 return ((Artifact) artifact).getClassifier();
308 public File resolve(Object artifact, Log log) {
309 ArtifactRequest request = new ArtifactRequest();
310 request.setArtifact((Artifact) artifact);
311 request.setRepositories(projectRepositories);
313 log.debug("Resolving artifact " + artifact + " from " + projectRepositories);
315 ArtifactResult result;
317 result = repositorySystem.resolveArtifact(repositorySystemSession, request);
318 } catch (ArtifactResolutionException e) {
319 log.warn("Cound not resolve " + artifact, e);
323 log.debug("Resolved artifact " + artifact + " to " + result.getArtifact().getFile() + " from " + result.getRepository());
325 return result.getArtifact().getFile();
329 public File resolveById(String id, Log log) throws MojoFailureException {
330 if (id.startsWith("mvn:")) {
331 if (id.contains("!")) {
332 id = id.substring(0, "mvn:".length()) + id.substring(id.indexOf("!") + 1);
334 if (id.endsWith("/")) {
335 id = id.substring(0, id.length() - 1);
338 id = MavenUtil.mvnToAether(id);
339 ArtifactRequest request = new ArtifactRequest();
340 request.setArtifact(new DefaultArtifact(id));
341 request.setRepositories(projectRepositories);
343 log.debug("Resolving artifact " + id + " from " + projectRepositories);
345 ArtifactResult result;
347 result = repositorySystem.resolveArtifact(repositorySystemSession, request);
348 } catch (ArtifactResolutionException e) {
349 log.warn("Could not resolve " + id, e);
350 throw new MojoFailureException(format("Couldn't resolve artifact %s", id), e);
353 log.debug("Resolved artifact " + id + " to " + result.getArtifact().getFile() + " from " + result.getRepository());
355 return result.getArtifact().getFile();
359 public String artifactToMvn(org.apache.maven.artifact.Artifact artifact, String versionOrRange) throws MojoExecutionException {
360 return this.artifactToMvn(toArtifact(artifact), versionOrRange);
364 public String artifactToMvn(Object _artifact, String versionOrRange) {
365 Artifact artifact = (Artifact) _artifact;
367 if (artifact.getExtension().equals("jar") && MavenUtil.isEmpty(artifact.getClassifier())) {
368 bundleName = String.format("mvn:%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange);
370 if (MavenUtil.isEmpty(artifact.getClassifier())) {
371 bundleName = String.format("mvn:%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension());
373 bundleName = String.format("mvn:%s/%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension(), artifact.getClassifier());
379 private static Artifact toArtifact(org.apache.maven.artifact.Artifact artifact) throws MojoExecutionException {
381 Method toArtifact = RepositoryUtils.class.getMethod("toArtifact", org.apache.maven.artifact.Artifact.class);
382 return (Artifact) toArtifact.invoke(null, artifact);
383 } catch (Exception e) {
384 throw new MojoExecutionException(e.getMessage(), e);
388 private static org.apache.maven.artifact.Artifact toArtifact(Artifact artifact) throws MojoExecutionException {
390 Method toArtifact = RepositoryUtils.class.getMethod("toArtifact", Artifact.class);
391 return (org.apache.maven.artifact.Artifact) toArtifact.invoke(null, artifact);
392 } catch (Exception e) {
393 throw new MojoExecutionException(e.getMessage(), e);
398 public org.apache.maven.artifact.Artifact mvnToArtifact(String name) throws MojoExecutionException {
399 name = MavenUtil.mvnToAether(name);
400 DefaultArtifact artifact = new DefaultArtifact(name);
401 org.apache.maven.artifact.Artifact mavenArtifact = toArtifact(artifact);
402 return mavenArtifact;
406 public String pathFromMaven(String name) throws MojoExecutionException {
407 if (name.indexOf(':') == -1) {
410 if (name.endsWith("/")) {
411 name = name.substring(0, name.length() - 1);
413 name = MavenUtil.mvnToAether(name);
414 return pathFromAether(name);
418 public String pathFromAether(String name) throws MojoExecutionException {
419 DefaultArtifact artifact = new DefaultArtifact(name);
420 org.apache.maven.artifact.Artifact mavenArtifact = toArtifact(artifact);
421 return MavenUtil.layout.pathOf(mavenArtifact);