| /* |
| * 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.karaf.tooling.utils; |
| |
| import org.apache.maven.RepositoryUtils; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.project.ProjectBuildingRequest; |
| import org.eclipse.aether.DefaultRepositorySystemSession; |
| import org.eclipse.aether.RepositorySystem; |
| import org.eclipse.aether.RepositorySystemSession; |
| import org.eclipse.aether.artifact.Artifact; |
| import org.eclipse.aether.artifact.DefaultArtifact; |
| import org.eclipse.aether.collection.*; |
| import org.eclipse.aether.graph.Dependency; |
| import org.eclipse.aether.graph.DependencyNode; |
| import org.eclipse.aether.repository.RemoteRepository; |
| import org.eclipse.aether.resolution.ArtifactRequest; |
| import org.eclipse.aether.resolution.ArtifactResolutionException; |
| import org.eclipse.aether.resolution.ArtifactResult; |
| import org.eclipse.aether.util.graph.selector.AndDependencySelector; |
| import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; |
| import org.eclipse.aether.util.graph.selector.OptionalDependencySelector; |
| import org.eclipse.aether.util.graph.transformer.*; |
| |
| import java.io.File; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.*; |
| |
| import static java.lang.String.*; |
| import static org.apache.commons.lang.reflect.MethodUtils.invokeMethod; |
| import static org.apache.karaf.deployer.kar.KarArtifactInstaller.FEATURE_CLASSIFIER; |
| |
| /** |
| * <p>{@link DependencyHelper} for accessing Eclipse Aether system used in Maven 3.1+. It uses reflection to access |
| * these methods of {@code maven-core} APIs which directly references Eclipse Aether classes.</p> |
| * |
| * <p>When {@code karaf-maven-plugin} switches to {@code maven-core:3.1.0+}, reflection should be use for Sonatype |
| * Aether in {@link Dependency30Helper} and this class will use Maven API directly.</p> |
| */ |
| public class Dependency31Helper implements DependencyHelper { |
| |
| /** |
| * The entry point to Aether, i.e. the component doing all the work. |
| */ |
| private final RepositorySystem repositorySystem; |
| |
| /** |
| * The current repository/network configuration of Maven. |
| */ |
| private final RepositorySystemSession repositorySystemSession; |
| |
| /** |
| * The project's remote repositories to use for the resolution of project dependencies. |
| */ |
| private final List<RemoteRepository> projectRepositories; |
| |
| // dependencies we are interested in |
| protected Set<LocalDependency> localDependencies; |
| // log of what happened during search |
| protected String treeListing; |
| |
| @SuppressWarnings("unchecked") |
| public Dependency31Helper(List<?> repositories, Object session, RepositorySystem repositorySystem) { |
| this.projectRepositories = (List<RemoteRepository>) repositories; |
| this.repositorySystemSession = (RepositorySystemSession) session; |
| this.repositorySystem = repositorySystem; |
| } |
| |
| public void setRepositorySession(final ProjectBuildingRequest request) throws MojoExecutionException { |
| try { |
| invokeMethod(request, "setRepositorySession", repositorySystemSession); |
| } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { |
| throw new MojoExecutionException("Cannot set repository session on project building request", e); |
| } |
| } |
| |
| @Override |
| public Set<LocalDependency> getLocalDependencies() { |
| return localDependencies; |
| } |
| |
| @Override |
| public String getTreeListing() { |
| return treeListing; |
| } |
| |
| @Override |
| public void getDependencies(MavenProject project, boolean useTransitiveDependencies) throws MojoExecutionException { |
| DependencyNode rootNode = getDependencyTree(toArtifact(project.getArtifact())); |
| |
| Scanner scanner = new Scanner(); |
| scanner.scan(rootNode, useTransitiveDependencies); |
| localDependencies = scanner.localDependencies; |
| treeListing = scanner.getLog(); |
| } |
| |
| private DependencyNode getDependencyTree(Artifact artifact) throws MojoExecutionException { |
| try { |
| CollectRequest collectRequest = new CollectRequest(new Dependency(artifact, "compile"), null, projectRepositories); |
| DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(repositorySystemSession); |
| session.setDependencySelector(new AndDependencySelector(new OptionalDependencySelector(), |
| new ScopeDependencySelector1(), |
| new ExclusionDependencySelector())); |
| // between aether-util 0.9.0.M1 and M2, JavaEffectiveScopeCalculator was removed |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=397241 |
| DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer(new ConflictMarker(), |
| new ConflictResolver(new NearestVersionSelector(), new JavaScopeSelector(), new SimpleOptionalitySelector(), new JavaScopeDeriver()), |
| new JavaDependencyContextRefiner()); |
| session.setDependencyGraphTransformer(transformer); |
| CollectResult result = repositorySystem.collectDependencies(session, collectRequest); |
| return result.getRoot(); |
| } catch (DependencyCollectionException e) { |
| throw new MojoExecutionException("Cannot build project dependency tree", e); |
| } |
| } |
| |
| /** |
| * Aether's ScopeDependencySelector appears to always exclude the configured scopes (test and provided) and there is no way to configure it to |
| * 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, |
| * it always starts by getting the child to apply to the project's dependencies. |
| */ |
| private static class ScopeDependencySelector1 implements DependencySelector { |
| |
| private DependencySelector child = new ScopeDependencySelector2(); |
| |
| public boolean selectDependency(Dependency dependency) { |
| throw new IllegalStateException("This does not appear to be called"); |
| } |
| |
| public DependencySelector deriveChildSelector(DependencyCollectionContext context) { |
| return child; |
| } |
| |
| } |
| |
| public static class ScopeDependencySelector2 implements DependencySelector { |
| |
| private DependencySelector child = new ScopeDependencySelector3(); |
| |
| public boolean selectDependency(Dependency dependency) { |
| String scope = dependency.getScope(); |
| return !"test".equals(scope); |
| } |
| |
| public DependencySelector deriveChildSelector(DependencyCollectionContext context) { |
| return child; |
| } |
| |
| } |
| |
| private static class ScopeDependencySelector3 implements DependencySelector { |
| |
| public boolean selectDependency(Dependency dependency) { |
| String scope = dependency.getScope(); |
| return !"test".equals(scope); |
| } |
| |
| public DependencySelector deriveChildSelector(DependencyCollectionContext context) { |
| return this; |
| } |
| |
| } |
| |
| private static class Scanner { |
| |
| private static enum Accept { |
| ACCEPT(true, true), |
| PROVIDED(true, false), |
| STOP(false, false); |
| |
| private final boolean more; |
| private final boolean local; |
| |
| private Accept(boolean more, boolean local) { |
| this.more = more; |
| this.local = local; |
| } |
| |
| public boolean isContinue() { |
| return more; |
| } |
| |
| public boolean isLocal() { |
| return local; |
| } |
| } |
| |
| // all the dependencies needed, with provided dependencies removed |
| private final Set<LocalDependency> localDependencies = new LinkedHashSet<>(); |
| |
| // dependencies from ancestor, to be removed from localDependencies |
| private final Set<Artifact> dependencies = new LinkedHashSet<>(); |
| |
| private final StringBuilder log = new StringBuilder(); |
| |
| public void scan(DependencyNode rootNode, boolean useTransitiveDependencies) throws MojoExecutionException { |
| for (DependencyNode child : rootNode.getChildren()) { |
| scan(rootNode, child, Accept.ACCEPT, useTransitiveDependencies, false, ""); |
| } |
| if (useTransitiveDependencies) { |
| localDependencies.removeAll(dependencies); |
| } |
| } |
| |
| private void scan(DependencyNode parentNode, DependencyNode dependencyNode, Accept parentAccept, boolean useTransitiveDependencies, boolean isFromFeature, String indent) throws MojoExecutionException { |
| Accept accept = accept(dependencyNode, parentAccept); |
| if (accept.isLocal()) { |
| if (isFromFeature) { |
| if (!isFeature(dependencyNode)) { |
| log.append(indent).append("from feature:").append(dependencyNode).append("\n"); |
| dependencies.add(dependencyNode.getDependency().getArtifact()); |
| } else { |
| log.append(indent).append("is feature:").append(dependencyNode).append("\n"); |
| } |
| } else { |
| log.append(indent).append("local:").append(dependencyNode).append("\n"); |
| if (localDependencies.contains(dependencyNode.getDependency().getArtifact())) { |
| log.append(indent).append("already in feature, returning:").append(dependencyNode).append("\n"); |
| return; |
| } |
| // TODO resolve scope conflicts |
| localDependencies.add(new LocalDependency(dependencyNode.getDependency().getScope(), dependencyNode.getDependency().getArtifact(), parentNode.getDependency().getArtifact())); |
| if (isFeature(dependencyNode) || !useTransitiveDependencies) { |
| isFromFeature = true; |
| } |
| } |
| if (useTransitiveDependencies && accept.isContinue()) { |
| List<DependencyNode> children = dependencyNode.getChildren(); |
| for (DependencyNode child : children) { |
| scan(dependencyNode, child, accept, useTransitiveDependencies, isFromFeature, indent + " "); |
| } |
| } |
| } |
| } |
| |
| public String getLog() { |
| return log.toString(); |
| } |
| |
| private Accept accept(DependencyNode dependency, Accept previous) { |
| String scope = dependency.getDependency().getScope(); |
| if (scope == null || "runtime".equalsIgnoreCase(scope) || "compile".equalsIgnoreCase(scope)) { |
| return previous; |
| } |
| if ("provided".equalsIgnoreCase(scope)) { |
| return Accept.PROVIDED; |
| } |
| return Accept.STOP; |
| } |
| |
| |
| } |
| |
| public static boolean isFeature(DependencyNode dependencyNode) { |
| return isFeature(dependencyNode.getDependency().getArtifact()); |
| } |
| |
| public static boolean isFeature(Artifact artifact) { |
| return artifact.getExtension().equals("kar") || FEATURE_CLASSIFIER.equals(artifact.getClassifier()); |
| } |
| |
| @Override |
| public boolean isArtifactAFeature(Object artifact) { |
| return Dependency31Helper.isFeature((Artifact) artifact); |
| } |
| |
| @Override |
| public String getBaseVersion(Object artifact) { |
| return ((Artifact) artifact).getBaseVersion(); |
| } |
| |
| @Override |
| public String getGroupId(Object artifact) { |
| return ((Artifact) artifact).getGroupId(); |
| } |
| |
| @Override |
| public String getArtifactId(Object artifact) { |
| return ((Artifact) artifact).getArtifactId(); |
| } |
| |
| @Override |
| public String getClassifier(Object artifact) { |
| return ((Artifact) artifact).getClassifier(); |
| } |
| |
| @Override |
| public File resolve(Object artifact, Log log) { |
| ArtifactRequest request = new ArtifactRequest(); |
| request.setArtifact((Artifact) artifact); |
| request.setRepositories(projectRepositories); |
| |
| log.debug("Resolving artifact " + artifact + " from " + projectRepositories); |
| |
| ArtifactResult result; |
| try { |
| result = repositorySystem.resolveArtifact(repositorySystemSession, request); |
| } catch (ArtifactResolutionException e) { |
| log.warn("Cound not resolve " + artifact, e); |
| return null; |
| } |
| |
| log.debug("Resolved artifact " + artifact + " to " + result.getArtifact().getFile() + " from " + result.getRepository()); |
| |
| return result.getArtifact().getFile(); |
| } |
| |
| @Override |
| public File resolveById(String id, Log log) throws MojoFailureException { |
| if (id.startsWith("mvn:")) { |
| if (id.contains("!")) { |
| id = id.substring(0, "mvn:".length()) + id.substring(id.indexOf("!") + 1); |
| } |
| if (id.endsWith("/")) { |
| id = id.substring(0, id.length() - 1); |
| } |
| } |
| id = MavenUtil.mvnToAether(id); |
| ArtifactRequest request = new ArtifactRequest(); |
| request.setArtifact(new DefaultArtifact(id)); |
| request.setRepositories(projectRepositories); |
| |
| log.debug("Resolving artifact " + id + " from " + projectRepositories); |
| |
| ArtifactResult result; |
| try { |
| result = repositorySystem.resolveArtifact(repositorySystemSession, request); |
| } catch (ArtifactResolutionException e) { |
| log.warn("Could not resolve " + id, e); |
| throw new MojoFailureException(format("Couldn't resolve artifact %s", id), e); |
| } |
| |
| log.debug("Resolved artifact " + id + " to " + result.getArtifact().getFile() + " from " + result.getRepository()); |
| |
| return result.getArtifact().getFile(); |
| } |
| |
| @Override |
| public String artifactToMvn(org.apache.maven.artifact.Artifact artifact, String versionOrRange) throws MojoExecutionException { |
| return this.artifactToMvn(toArtifact(artifact), versionOrRange); |
| } |
| |
| @Override |
| public String artifactToMvn(Object _artifact, String versionOrRange) { |
| Artifact artifact = (Artifact) _artifact; |
| String bundleName; |
| if (artifact.getExtension().equals("jar") && MavenUtil.isEmpty(artifact.getClassifier())) { |
| bundleName = String.format("mvn:%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange); |
| } else { |
| if (MavenUtil.isEmpty(artifact.getClassifier())) { |
| bundleName = String.format("mvn:%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension()); |
| } else { |
| bundleName = String.format("mvn:%s/%s/%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), versionOrRange, artifact.getExtension(), artifact.getClassifier()); |
| } |
| } |
| return bundleName; |
| } |
| |
| private static Artifact toArtifact(org.apache.maven.artifact.Artifact artifact) throws MojoExecutionException { |
| try { |
| Method toArtifact = RepositoryUtils.class.getMethod("toArtifact", org.apache.maven.artifact.Artifact.class); |
| return (Artifact) toArtifact.invoke(null, artifact); |
| } catch (Exception e) { |
| throw new MojoExecutionException(e.getMessage(), e); |
| } |
| } |
| |
| private static org.apache.maven.artifact.Artifact toArtifact(Artifact artifact) throws MojoExecutionException { |
| try { |
| Method toArtifact = RepositoryUtils.class.getMethod("toArtifact", Artifact.class); |
| return (org.apache.maven.artifact.Artifact) toArtifact.invoke(null, artifact); |
| } catch (Exception e) { |
| throw new MojoExecutionException(e.getMessage(), e); |
| } |
| } |
| |
| @Override |
| public org.apache.maven.artifact.Artifact mvnToArtifact(String name) throws MojoExecutionException { |
| name = MavenUtil.mvnToAether(name); |
| DefaultArtifact artifact = new DefaultArtifact(name); |
| org.apache.maven.artifact.Artifact mavenArtifact = toArtifact(artifact); |
| return mavenArtifact; |
| } |
| |
| @Override |
| public String pathFromMaven(String name) throws MojoExecutionException { |
| if (name.indexOf(':') == -1) { |
| return name; |
| } |
| if (name.endsWith("/")) { |
| name = name.substring(0, name.length() - 1); |
| } |
| name = MavenUtil.mvnToAether(name); |
| return pathFromAether(name); |
| } |
| |
| @Override |
| public String pathFromAether(String name) throws MojoExecutionException { |
| DefaultArtifact artifact = new DefaultArtifact(name); |
| org.apache.maven.artifact.Artifact mavenArtifact = toArtifact(artifact); |
| return MavenUtil.layout.pathOf(mavenArtifact); |
| } |
| |
| } |