/*
* Copyright 2015 Lukas Krejci
*
* 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.revapi.standalone;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.settings.Settings;
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.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.util.graph.selector.StaticDependencySelector;
import org.eclipse.aether.version.Version;
import org.revapi.maven.utils.ScopeDependencySelector;
import org.revapi.maven.utils.ScopeDependencyTraverser;
import org.jboss.forge.furnace.addons.AddonId;
import org.jboss.forge.furnace.impl.modules.providers.FurnaceContainerSpec;
import org.jboss.forge.furnace.manager.maven.MavenContainer;
import org.jboss.forge.furnace.manager.maven.addon.AddonInfoBuilder;
import org.jboss.forge.furnace.manager.maven.addon.MavenAddonDependencyResolver;
import org.jboss.forge.furnace.manager.maven.result.MavenResponseBuilder;
import org.jboss.forge.furnace.manager.maven.util.MavenRepositories;
import org.jboss.forge.furnace.manager.spi.AddonInfo;
import org.jboss.forge.furnace.manager.spi.Response;
import org.jboss.forge.furnace.versions.Versions;
/**
* @author Lukas Krejci
* @since 0.1
*/
final class ExtensionResolver extends MavenAddonDependencyResolver {
public static final String REVAPI_GROUP_ID = "org.revapi";
public static final String REVAPI_ARTIFACT_ID = "revapi";
public static final Set<String> ARTIFACTS_IN_LIB = new HashSet<>();
public static void init() {
FurnaceContainerSpec.paths.add("javax/annotation/processing");
initLibJar("org.revapi:revapi", "org/revapi", "org/revapi/simple",
"org/revapi/query", "org/revapi/configuration");
initLibJar("com.google.code.findbugs:jsr305", "java/annotation", "javax/annotation/concurrent",
"javax/annotation/meta");
initLibJar("org.slf4j:slf4j-api", "org/slf4j", "org/slf4j/helpers", "org/slf4j/spi");
initLibJar("org.jboss:jboss-dmr", "org/jboss/dmr");
}
private static void initLibJar(String groupArtifact, String... packages) {
ARTIFACTS_IN_LIB.add(groupArtifact);
for (String pkg : packages) {
FurnaceContainerSpec.paths.add(pkg);
}
}
private Settings settings;
private final MavenContainer container = new MavenContainer();
private final ScopeDependencyTraverser dependencyTraverser = new ScopeDependencyTraverser("compile", "provided") {
@Override
public boolean traverseDependency(Dependency dependency) {
return super.traverseDependency(dependency) && !ExtensionResolver.isRevapiApi(dependency);
}
};
private final ScopeDependencySelector dependencySelector = new ScopeDependencySelector("compile", "provided") {
@Override
public boolean selectDependency(Dependency dependency) {
return super.selectDependency(dependency) && !ExtensionResolver.isRevapiApi(dependency);
}
};
public ExtensionResolver() {
}
public static boolean isRevapiApi(Dependency dep) {
return isRevapiApi(dep.getArtifact());
}
public static boolean isRevapiApi(Artifact artifact) {
return ARTIFACTS_IN_LIB.contains(artifact.getGroupId() + ":" + artifact.getArtifactId());
}
@Override
public AddonInfo resolveAddonDependencyHierarchy(AddonId addonId) {
String coords = toMavenCoords(addonId);
RepositorySystem system = container.getRepositorySystem();
Settings settings = getSettings();
DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
DependencyNode dependencyNode = traverseAddonGraph(coords, system, settings, session);
return fromNode(addonId, dependencyNode, system, settings, session);
}
@Override
public Response<File[]> resolveResources(final AddonId addonId) {
RepositorySystem system = container.getRepositorySystem();
Settings settings = getSettings();
DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
final String mavenCoords = toMavenCoords(addonId);
Artifact queryArtifact = new DefaultArtifact(mavenCoords);
session.setDependencyTraverser(dependencyTraverser);
session.setDependencySelector(dependencySelector);
Dependency dependency = new Dependency(queryArtifact, null);
List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
CollectRequest collectRequest = new CollectRequest(dependency, repositories);
DependencyResult result;
try {
result = system.resolveDependencies(session, new DependencyRequest(collectRequest, null));
} catch (DependencyResolutionException e) {
throw new RuntimeException(e);
}
List<Exception> collectExceptions = result.getCollectExceptions();
Set<File> files = new HashSet<File>();
List<ArtifactResult> artifactResults = result.getArtifactResults();
for (ArtifactResult artifactResult : artifactResults) {
Artifact artifact = artifactResult.getArtifact();
if (!mavenCoords.equals(artifact.toString())) {
continue;
}
files.add(artifact.getFile());
}
return new MavenResponseBuilder<File[]>(files.toArray(new File[files.size()])).setExceptions(collectExceptions);
}
@Override
public Response<AddonId[]> resolveVersions(final String addonName) {
String addonNameSplit;
String version;
String[] split = addonName.split(",");
if (split.length == 2) {
addonNameSplit = split[0];
version = split[1];
} else {
addonNameSplit = addonName;
version = null;
}
RepositorySystem system = container.getRepositorySystem();
Settings settings = getSettings();
DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
VersionRangeResult versions = getVersions(system, settings, session, repositories, addonNameSplit, version);
List<Exception> exceptions = versions.getExceptions();
List<Version> versionsList = versions.getVersions();
List<AddonId> addons = new ArrayList<AddonId>();
List<AddonId> snapshots = new ArrayList<AddonId>();
for (Version artifactVersion : versionsList) {
AddonId addonId = AddonId.from(addonName, artifactVersion.toString());
if (Versions.isSnapshot(addonId.getVersion())) {
snapshots.add(addonId);
} else {
addons.add(addonId);
}
}
if (addons.isEmpty()) {
addons = snapshots;
}
return new MavenResponseBuilder<AddonId[]>(addons.toArray(new AddonId[addons.size()]))
.setExceptions(exceptions);
}
@Override
public Response<String> resolveAPIVersion(AddonId addonId) {
RepositorySystem system = container.getRepositorySystem();
Settings settings = getSettings();
DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
String mavenCoords = toMavenCoords(addonId);
Artifact queryArtifact = new DefaultArtifact(mavenCoords);
session.setDependencyTraverser(new ScopeDependencyTraverser("compile", "provided"));
session.setDependencySelector(new StaticDependencySelector(true));
CollectRequest request = new CollectRequest(new Dependency(queryArtifact, null), repositories);
CollectResult result;
try {
result = system.collectDependencies(session, request);
} catch (DependencyCollectionException e) {
throw new RuntimeException(e);
}
List<Exception> exceptions = result.getExceptions();
String apiVersion = findVersion(result.getRoot().getChildren(), REVAPI_GROUP_ID, REVAPI_ARTIFACT_ID);
return new MavenResponseBuilder<>(apiVersion).setExceptions(exceptions);
}
private String findVersion(List<DependencyNode> dependencies, String groupId, String artifactId) {
for (DependencyNode child : dependencies) {
Artifact childArtifact = child.getArtifact();
if (groupId.equals(childArtifact.getGroupId())
&& artifactId.equals(childArtifact.getArtifactId())) {
return childArtifact.getBaseVersion();
} else {
String version = findVersion(child.getChildren(), groupId, artifactId);
if (version != null) {
return version;
}
}
}
return null;
}
private VersionRangeResult getVersions(RepositorySystem system, Settings settings, RepositorySystemSession session,
List<RemoteRepository> repositories,
String addonName,
String version) {
try {
String[] split = addonName.split(",");
if (split.length == 2) {
version = split[1];
}
if (version == null || version.isEmpty()) {
version = "[,)";
} else if (!version.matches("(\\(|\\[).*?(\\)|\\])")) {
version = "[" + version + "]";
}
Artifact artifact = new DefaultArtifact(toMavenCoords(AddonId.from(addonName, version)));
VersionRangeRequest rangeRequest = new VersionRangeRequest(artifact, repositories, null);
VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest);
return rangeResult;
} catch (Exception e) {
throw new RuntimeException("Failed to look up versions for [" + addonName + "]", e);
}
}
private AddonInfo fromNode(AddonId id, DependencyNode dependencyNode, RepositorySystem system, Settings settings,
DefaultRepositorySystemSession session) {
AddonInfoBuilder builder = AddonInfoBuilder.from(id);
List<DependencyNode> children = dependencyNode.getChildren();
for (DependencyNode child : children) {
Dependency dependency = child.getDependency();
Artifact artifact = dependency.getArtifact();
if (!"jar".equals(artifact.getExtension())) {
//skip non-jar dependencies - furnace doesn't work with them and they do not provide code (hopefully)
continue;
}
AddonId childId = toAddonId(artifact);
boolean exported = false;
boolean optional = dependency.isOptional();
String scope = dependency.getScope();
if (scope != null && !optional) {
if ("compile".equalsIgnoreCase(scope) || "runtime".equalsIgnoreCase(scope)) {
exported = true;
} else if ("provided".equalsIgnoreCase(scope)) {
exported = false;
}
}
DependencyNode node = traverseAddonGraph(toMavenCoords(childId), system, settings, session);
AddonInfo addonInfo = fromNode(childId, node, system, settings, session);
if (optional) {
builder.addOptionalDependency(addonInfo, exported);
} else {
builder.addRequiredDependency(addonInfo, exported);
}
}
return new LazyAddonInfo(this, builder);
}
private DependencyNode traverseAddonGraph(String coords, RepositorySystem system, Settings settings,
DefaultRepositorySystemSession session) {
session.setDependencyTraverser(dependencyTraverser);
session.setDependencySelector(dependencySelector);
Artifact queryArtifact = new DefaultArtifact(coords);
List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
CollectRequest collectRequest = new CollectRequest(new Dependency(queryArtifact, null), repositories);
CollectResult result;
try {
result = system.collectDependencies(session, collectRequest);
} catch (DependencyCollectionException e) {
throw new RuntimeException(e);
}
return result.getRoot();
}
private String toMavenCoords(AddonId addonId) {
String coords = addonId.getName() + ":jar:" + addonId.getVersion();
return coords;
}
private AddonId toAddonId(Artifact artifact) {
return AddonId.from(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact.getBaseVersion());
}
/**
* @param settings the settings to set
*/
public void setSettings(Settings settings) {
this.settings = settings;
}
/**
* @return the settings
*/
public Settings getSettings() {
return settings == null ? container.getSettings() : settings;
}
}