Add .tox/ to .gitignore
[odlparent.git] / karaf / karaf-maven-plugin / src / main / java / org / apache / karaf / tooling / utils / Dependency31Helper.java
1 /*
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
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
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
17  * under the License.
18  */
19 package org.apache.karaf.tooling.utils;
20
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.*;
43
44 import java.io.File;
45 import java.lang.reflect.InvocationTargetException;
46 import java.lang.reflect.Method;
47 import java.util.*;
48
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;
52
53 /**
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>
56  *
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>
59  */
60 public class Dependency31Helper implements DependencyHelper {
61
62     /**
63      * The entry point to Aether, i.e. the component doing all the work.
64      */
65     private final RepositorySystem repositorySystem;
66
67     /**
68      * The current repository/network configuration of Maven.
69      */
70     private final RepositorySystemSession repositorySystemSession;
71
72     /**
73      * The project's remote repositories to use for the resolution of project dependencies.
74      */
75     private final List<RemoteRepository> projectRepositories;
76
77     // dependencies we are interested in
78     protected Set<LocalDependency> localDependencies;
79     // log of what happened during search
80     protected String treeListing;
81
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;
87     }
88     
89         public void setRepositorySession(final ProjectBuildingRequest request) throws MojoExecutionException {
90                 try {
91                         invokeMethod(request, "setRepositorySession", repositorySystemSession);
92                 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
93                         throw new MojoExecutionException("Cannot set repository session on project building request", e);
94                 }
95         }
96
97     @Override
98     public Set<LocalDependency> getLocalDependencies() {
99         return localDependencies;
100     }
101
102     @Override
103     public String getTreeListing() {
104         return treeListing;
105     }
106
107     @Override
108     public void getDependencies(MavenProject project, boolean useTransitiveDependencies) throws MojoExecutionException {
109         DependencyNode rootNode = getDependencyTree(toArtifact(project.getArtifact()));
110
111         Scanner scanner = new Scanner();
112         scanner.scan(rootNode, useTransitiveDependencies);
113         localDependencies = scanner.localDependencies;
114         treeListing = scanner.getLog();
115     }
116
117     private DependencyNode getDependencyTree(Artifact artifact) throws MojoExecutionException {
118         try {
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);
134         }
135     }
136
137     /**
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.
141      */
142     private static class ScopeDependencySelector1 implements DependencySelector {
143
144         private DependencySelector child = new ScopeDependencySelector2();
145
146         public boolean selectDependency(Dependency dependency) {
147             throw new IllegalStateException("This does not appear to be called");
148         }
149
150         public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
151             return child;
152         }
153
154     }
155
156     public static class ScopeDependencySelector2 implements DependencySelector {
157
158         private DependencySelector child = new ScopeDependencySelector3();
159
160         public boolean selectDependency(Dependency dependency) {
161             String scope = dependency.getScope();
162             return !"test".equals(scope) && !"runtime".equals(scope);
163         }
164
165         public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
166             return child;
167         }
168
169     }
170
171     private static class ScopeDependencySelector3 implements DependencySelector {
172
173         public boolean selectDependency(Dependency dependency) {
174             String scope = dependency.getScope();
175             return !"test".equals(scope) && !"provided".equals(scope) && !"runtime".equals(scope);
176         }
177
178         public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
179             return this;
180         }
181
182     }
183
184     private static class Scanner {
185
186         private static enum Accept {
187             ACCEPT(true, true),
188             PROVIDED(true, false),
189             STOP(false, false);
190
191             private final boolean more;
192             private final boolean local;
193
194             private Accept(boolean more, boolean local) {
195                 this.more = more;
196                 this.local = local;
197             }
198
199             public boolean isContinue() {
200                 return more;
201             }
202
203             public boolean isLocal() {
204                 return local;
205             }
206         }
207
208         // all the dependencies needed, with provided dependencies removed
209         private final Set<LocalDependency> localDependencies = new LinkedHashSet<>();
210
211         // dependencies from ancestor, to be removed from localDependencies
212         private final Set<Artifact> dependencies = new LinkedHashSet<>();
213
214         private final StringBuilder log = new StringBuilder();
215
216         public void scan(DependencyNode rootNode, boolean useTransitiveDependencies) throws MojoExecutionException {
217             for (DependencyNode child : rootNode.getChildren()) {
218                 scan(rootNode, child, Accept.ACCEPT, useTransitiveDependencies, false, "");
219             }
220             if (useTransitiveDependencies) {
221                 localDependencies.removeAll(dependencies);
222             }
223         }
224
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()) {
228                 if (isFromFeature) {
229                     if (!isFeature(dependencyNode)) {
230                         log.append(indent).append("from feature:").append(dependencyNode).append("\n");
231                         dependencies.add(dependencyNode.getDependency().getArtifact());
232                     } else {
233                         log.append(indent).append("is feature:").append(dependencyNode).append("\n");
234                     }
235                 } else {
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");
239                         return;
240                     }
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;
245                     }
246                 }
247                 if (useTransitiveDependencies && accept.isContinue()) {
248                     List<DependencyNode> children = dependencyNode.getChildren();
249                     for (DependencyNode child : children) {
250                         scan(dependencyNode, child, accept, useTransitiveDependencies, isFromFeature, indent + " ");
251                     }
252                 }
253             }
254         }
255
256         public String getLog() {
257             return log.toString();
258         }
259
260         private Accept accept(DependencyNode dependency, Accept previous) {
261             String scope = dependency.getDependency().getScope();
262             if (scope == null || "runtime".equalsIgnoreCase(scope) || "compile".equalsIgnoreCase(scope)) {
263                 return previous;
264             }
265             if ("provided".equalsIgnoreCase(scope)) {
266                 return Accept.PROVIDED;
267             }
268             return Accept.STOP;
269         }
270
271
272     }
273
274     public static boolean isFeature(DependencyNode dependencyNode) {
275         return isFeature(dependencyNode.getDependency().getArtifact());
276     }
277
278     public static boolean isFeature(Artifact artifact) {
279         return artifact.getExtension().equals("kar") || FEATURE_CLASSIFIER.equals(artifact.getClassifier());
280     }
281
282     @Override
283     public boolean isArtifactAFeature(Object artifact) {
284         return Dependency31Helper.isFeature((Artifact) artifact);
285     }
286     
287         @Override
288         public String getBaseVersion(Object artifact) {
289                 return ((Artifact) artifact).getBaseVersion();
290         }
291
292         @Override
293         public String getGroupId(Object artifact) {
294                 return ((Artifact) artifact).getGroupId();
295         }
296
297     @Override
298     public String getArtifactId(Object artifact) {
299         return ((Artifact) artifact).getArtifactId();
300     }
301
302     @Override
303     public String getClassifier(Object artifact) {
304         return ((Artifact) artifact).getClassifier();
305     }
306
307     @Override
308     public File resolve(Object artifact, Log log) {
309         ArtifactRequest request = new ArtifactRequest();
310         request.setArtifact((Artifact) artifact);
311         request.setRepositories(projectRepositories);
312
313         log.debug("Resolving artifact " + artifact + " from " + projectRepositories);
314
315         ArtifactResult result;
316         try {
317             result = repositorySystem.resolveArtifact(repositorySystemSession, request);
318         } catch (ArtifactResolutionException e) {
319             log.warn("Cound not resolve " + artifact, e);
320             return null;
321         }
322
323         log.debug("Resolved artifact " + artifact + " to " + result.getArtifact().getFile() + " from " + result.getRepository());
324
325         return result.getArtifact().getFile();
326     }
327
328     @Override
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);
333             }
334             if (id.endsWith("/")) {
335                 id = id.substring(0, id.length() - 1);
336             }
337         }
338         id = MavenUtil.mvnToAether(id);
339         ArtifactRequest request = new ArtifactRequest();
340         request.setArtifact(new DefaultArtifact(id));
341         request.setRepositories(projectRepositories);
342
343         log.debug("Resolving artifact " + id + " from " + projectRepositories);
344
345         ArtifactResult result;
346         try {
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);
351         }
352
353         log.debug("Resolved artifact " + id + " to " + result.getArtifact().getFile() + " from " + result.getRepository());
354
355         return result.getArtifact().getFile();
356     }
357
358     @Override
359     public String artifactToMvn(org.apache.maven.artifact.Artifact artifact, String versionOrRange) throws MojoExecutionException {
360         return this.artifactToMvn(toArtifact(artifact), versionOrRange);
361     }
362
363     @Override
364     public String artifactToMvn(Object _artifact, String versionOrRange) {
365         Artifact artifact = (Artifact) _artifact;
366         String bundleName;
367         if (artifact.getExtension().equals("jar") && MavenUtil.isEmpty(artifact.getClassifier())) {
368             bundleName = String.format("mvn:%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange);
369         } else {
370             if (MavenUtil.isEmpty(artifact.getClassifier())) {
371                 bundleName = String.format("mvn:%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension());
372             } else {
373                 bundleName = String.format("mvn:%s/%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension(), artifact.getClassifier());
374             }
375         }
376         return bundleName;
377     }
378
379     private static Artifact toArtifact(org.apache.maven.artifact.Artifact artifact) throws MojoExecutionException {
380         try {
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);
385         }
386     }
387
388     private static org.apache.maven.artifact.Artifact toArtifact(Artifact artifact) throws MojoExecutionException {
389         try {
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);
394         }
395     }
396
397     @Override
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;
403     }
404
405     @Override
406     public String pathFromMaven(String name) throws MojoExecutionException {
407         if (name.indexOf(':') == -1) {
408             return name;
409         }
410         if (name.endsWith("/")) {
411             name = name.substring(0, name.length() - 1);
412         }
413         name = MavenUtil.mvnToAether(name);
414         return pathFromAether(name);
415     }
416
417     @Override
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);
422     }
423
424 }