/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed 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.springframework.boot.cli.compiler.grape;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import groovy.grape.GrapeEngine;
import groovy.lang.GroovyClassLoader;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
/**
* A {@link GrapeEngine} implementation that uses
* <a href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used
* by Maven.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
@SuppressWarnings("rawtypes")
public class AetherGrapeEngine implements GrapeEngine {
private static final Collection<Exclusion> WILDCARD_EXCLUSION;
static {
List<Exclusion> exclusions = new ArrayList<>();
exclusions.add(new Exclusion("*", "*", "*", "*"));
WILDCARD_EXCLUSION = Collections.unmodifiableList(exclusions);
}
private final DependencyResolutionContext resolutionContext;
private final ProgressReporter progressReporter;
private final GroovyClassLoader classLoader;
private final DefaultRepositorySystemSession session;
private final RepositorySystem repositorySystem;
private final List<RemoteRepository> repositories;
public AetherGrapeEngine(GroovyClassLoader classLoader,
RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession,
List<RemoteRepository> remoteRepositories,
DependencyResolutionContext resolutionContext, boolean quiet) {
this.classLoader = classLoader;
this.repositorySystem = repositorySystem;
this.session = repositorySystemSession;
this.resolutionContext = resolutionContext;
this.repositories = new ArrayList<>();
List<RemoteRepository> remotes = new ArrayList<>(remoteRepositories);
Collections.reverse(remotes); // priority is reversed in addRepository
for (RemoteRepository repository : remotes) {
addRepository(repository);
}
this.progressReporter = getProgressReporter(this.session, quiet);
}
private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session,
boolean quiet) {
String progressReporter = (quiet ? "none"
: System.getProperty(
"org.springframework.boot.cli.compiler.grape.ProgressReporter"));
if ("detail".equals(progressReporter)
|| Boolean.getBoolean("groovy.grape.report.downloads")) {
return new DetailedProgressReporter(session, System.out);
}
else if ("none".equals(progressReporter)) {
return new ProgressReporter() {
@Override
public void finished() {
}
};
}
else {
return new SummaryProgressReporter(session, System.out);
}
}
@Override
public Object grab(Map args) {
return grab(args, args);
}
@Override
public Object grab(Map args, Map... dependencyMaps) {
List<Exclusion> exclusions = createExclusions(args);
List<Dependency> dependencies = createDependencies(dependencyMaps, exclusions);
try {
List<File> files = resolve(dependencies);
GroovyClassLoader classLoader = getClassLoader(args);
for (File file : files) {
classLoader.addURL(file.toURI().toURL());
}
}
catch (ArtifactResolutionException ex) {
throw new DependencyResolutionFailedException(ex);
}
catch (MalformedURLException ex) {
throw new DependencyResolutionFailedException(ex);
}
return null;
}
@SuppressWarnings("unchecked")
private List<Exclusion> createExclusions(Map<?, ?> args) {
List<Exclusion> exclusions = new ArrayList<>();
if (args != null) {
List<Map<String, Object>> exclusionMaps = (List<Map<String, Object>>) args
.get("excludes");
if (exclusionMaps != null) {
for (Map<String, Object> exclusionMap : exclusionMaps) {
exclusions.add(createExclusion(exclusionMap));
}
}
}
return exclusions;
}
private Exclusion createExclusion(Map<String, Object> exclusionMap) {
String group = (String) exclusionMap.get("group");
String module = (String) exclusionMap.get("module");
return new Exclusion(group, module, "*", "*");
}
private List<Dependency> createDependencies(Map<?, ?>[] dependencyMaps,
List<Exclusion> exclusions) {
List<Dependency> dependencies = new ArrayList<>(dependencyMaps.length);
for (Map<?, ?> dependencyMap : dependencyMaps) {
dependencies.add(createDependency(dependencyMap, exclusions));
}
return dependencies;
}
private Dependency createDependency(Map<?, ?> dependencyMap,
List<Exclusion> exclusions) {
Artifact artifact = createArtifact(dependencyMap);
if (isTransitive(dependencyMap)) {
return new Dependency(artifact, JavaScopes.COMPILE, false, exclusions);
}
return new Dependency(artifact, JavaScopes.COMPILE, null, WILDCARD_EXCLUSION);
}
private Artifact createArtifact(Map<?, ?> dependencyMap) {
String group = (String) dependencyMap.get("group");
String module = (String) dependencyMap.get("module");
String version = (String) dependencyMap.get("version");
if (version == null) {
version = this.resolutionContext.getManagedVersion(group, module);
}
String classifier = (String) dependencyMap.get("classifier");
String type = determineType(dependencyMap);
return new DefaultArtifact(group, module, classifier, type, version);
}
private String determineType(Map<?, ?> dependencyMap) {
String type = (String) dependencyMap.get("type");
String ext = (String) dependencyMap.get("ext");
if (type == null) {
type = ext;
if (type == null) {
type = "jar";
}
}
else if (ext != null && !type.equals(ext)) {
throw new IllegalArgumentException(
"If both type and ext are specified they must have the same value");
}
return type;
}
private boolean isTransitive(Map<?, ?> dependencyMap) {
Boolean transitive = (Boolean) dependencyMap.get("transitive");
return (transitive == null ? true : transitive);
}
private List<Dependency> getDependencies(DependencyResult dependencyResult) {
List<Dependency> dependencies = new ArrayList<>();
for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) {
dependencies.add(
new Dependency(artifactResult.getArtifact(), JavaScopes.COMPILE));
}
return dependencies;
}
private List<File> getFiles(DependencyResult dependencyResult) {
List<File> files = new ArrayList<>();
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
files.add(result.getArtifact().getFile());
}
return files;
}
private GroovyClassLoader getClassLoader(Map args) {
GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader");
return (classLoader == null ? this.classLoader : classLoader);
}
@Override
public void addResolver(Map<String, Object> args) {
String name = (String) args.get("name");
String root = (String) args.get("root");
RemoteRepository.Builder builder = new RemoteRepository.Builder(name, "default",
root);
RemoteRepository repository = builder.build();
addRepository(repository);
}
protected void addRepository(RemoteRepository repository) {
if (this.repositories.contains(repository)) {
return;
}
repository = getPossibleMirror(repository);
repository = applyProxy(repository);
repository = applyAuthentication(repository);
this.repositories.add(0, repository);
}
private RemoteRepository getPossibleMirror(RemoteRepository remoteRepository) {
RemoteRepository mirror = this.session.getMirrorSelector()
.getMirror(remoteRepository);
if (mirror != null) {
return mirror;
}
return remoteRepository;
}
private RemoteRepository applyProxy(RemoteRepository repository) {
if (repository.getProxy() == null) {
RemoteRepository.Builder builder = new RemoteRepository.Builder(repository);
builder.setProxy(this.session.getProxySelector().getProxy(repository));
repository = builder.build();
}
return repository;
}
private RemoteRepository applyAuthentication(RemoteRepository repository) {
if (repository.getAuthentication() == null) {
RemoteRepository.Builder builder = new RemoteRepository.Builder(repository);
builder.setAuthentication(this.session.getAuthenticationSelector()
.getAuthentication(repository));
repository = builder.build();
}
return repository;
}
@Override
public Map<String, Map<String, List<String>>> enumerateGrapes() {
throw new UnsupportedOperationException("Grape enumeration is not supported");
}
@Override
public URI[] resolve(Map args, Map... dependencyMaps) {
return this.resolve(args, null, dependencyMaps);
}
@Override
public URI[] resolve(Map args, List depsInfo, Map... dependencyMaps) {
List<Exclusion> exclusions = createExclusions(args);
List<Dependency> dependencies = createDependencies(dependencyMaps, exclusions);
try {
List<File> files = resolve(dependencies);
List<URI> uris = new ArrayList<>(files.size());
for (File file : files) {
uris.add(file.toURI());
}
return uris.toArray(new URI[uris.size()]);
}
catch (Exception ex) {
throw new DependencyResolutionFailedException(ex);
}
}
private List<File> resolve(List<Dependency> dependencies)
throws ArtifactResolutionException {
try {
CollectRequest collectRequest = getCollectRequest(dependencies);
DependencyRequest dependencyRequest = getDependencyRequest(collectRequest);
DependencyResult result = this.repositorySystem
.resolveDependencies(this.session, dependencyRequest);
addManagedDependencies(result);
return getFiles(result);
}
catch (Exception ex) {
throw new DependencyResolutionFailedException(ex);
}
finally {
this.progressReporter.finished();
}
}
private CollectRequest getCollectRequest(List<Dependency> dependencies) {
CollectRequest collectRequest = new CollectRequest((Dependency) null,
dependencies, new ArrayList<>(this.repositories));
collectRequest
.setManagedDependencies(this.resolutionContext.getManagedDependencies());
return collectRequest;
}
private DependencyRequest getDependencyRequest(CollectRequest collectRequest) {
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest,
DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE,
JavaScopes.RUNTIME));
return dependencyRequest;
}
private void addManagedDependencies(DependencyResult result) {
this.resolutionContext.addManagedDependencies(getDependencies(result));
}
@Override
public Map[] listDependencies(ClassLoader classLoader) {
throw new UnsupportedOperationException("Listing dependencies is not supported");
}
@Override
public Object grab(String endorsedModule) {
throw new UnsupportedOperationException(
"Grabbing an endorsed module is not supported");
}
}