/**
* Copyright (c) 2012-2013, JCabi.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. 3) Neither the name of the jcabi.com nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.chaschev.install.jcabi;
import com.chaschev.chutils.util.Exceptions;
import com.jcabi.aspects.Loggable;
import com.jcabi.log.Logger;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.internal.MavenRepositorySystemSession;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.collection.CollectRequest;
import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.graph.DependencyFilter;
import org.sonatype.aether.repository.Authentication;
import org.sonatype.aether.repository.LocalRepository;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.resolution.*;
import org.sonatype.aether.util.filter.DependencyFilterUtils;
import javax.annotation.Nonnull;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Resolver of dependencies for one artifact.
* <p/>
* <p>You need the following dependencies to have in classpath in order to
* to work with this class:
* <p/>
* <pre>
* org.sonatype.aether:aether-api:1.13.1
* org.apache.maven:maven-core:3.0.3
* </pre>
*
* @author Yegor Bugayenko (yegor@tpc2.com)
* @version $Id$
* @checkstyle ClassDataAbstractionCoupling (500 lines)
* @todo #143 This class should be @Immutable, but RemoteRepository is
* not immutable. Let's create a new class to encapsulate all necessary
* properties from RemoteRepository.
* @see <a href="http://sonatype.github.com/sonatype-aether/apidocs/overview-tree.html">Aether 1.13.1 JavaDoc</a>
* @see com.jcabi.aether.Classpath
* @since 0.1.6
*/
@ToString
@EqualsAndHashCode(of = {"remotes", "localRepo"})
@Loggable(Loggable.DEBUG)
public final class Aether {
/**
* Remote project repositories.
*/
private final transient RemoteRepository[] remotes;
/**
* Location of local repository.
*/
private final transient File localRepo;
/**
* Repository system.
*/
private final transient RepositorySystem system =
new RepositorySystemBuilder().build();
/**
* Public ctor, requires information about all remote repositories and one
* local.
*
* @param prj The Maven project
* @param repo Local repository location (directory path)
*/
public Aether(@Nonnull final MavenProject prj, @Nonnull final File repo) {
this(prj.getRemoteProjectRepositories(), repo);
}
/**
* Public ctor, requires information about all remote repositories and one
* local.
*
* @param repos Collection of remote repositories
* @param repo Local repository location (directory path)
* @since 0.8
*/
public Aether(@Nonnull final Collection<RemoteRepository> repos,
@Nonnull final File repo) {
this.remotes = repos.toArray(new RemoteRepository[]{});
this.localRepo = repo;
}
/**
* List of transitive dependencies of the artifact.
*
*
* @param root The artifact to work with
* @param scope The scope to work with ("runtime", "test", etc.)
* @return The list of dependencies
* @throws org.sonatype.aether.resolution.DependencyResolutionException
* If can't fetch it
* @todo #51 This "filter IF NOT NULL" validation is a workaround,
* since I don't
* know what the actual problem is. Looks like sometimes (for some unknown
* reason) #classpathFilter() returns NULL. When exactly this may happen
* I have no idea. That's why this workaround. Sometime later we should
* do a proper testing and reproduce this defect in a test.
*/
public DependencyResult resolve(@Nonnull final Artifact root,
@Nonnull final String scope) throws DependencyResolutionException {
final DependencyFilter filter =
DependencyFilterUtils.classpathFilter(scope);
if (filter == null) {
throw new IllegalStateException(
String.format("failed to create a filter for '%s'", scope)
);
}
return this.resolve(root, scope, filter);
}
/**
* List of transitive dependencies of the artifact.
*
* @param root The artifact to work with
* @param scope The scope to work with ("runtime", "test", etc.)
* @param filter The dependency filter to work with
* @return The list of dependencies
* @throws org.sonatype.aether.resolution.DependencyResolutionException
* If can't fetch it
*/
public DependencyResult resolve(
@Nonnull final Artifact root,
@Nonnull final String scope, @Nonnull final DependencyFilter filter) throws DependencyResolutionException {
final Dependency rdep = new Dependency(root, scope);
final CollectRequest crq = this.request(rdep);
return this.fetch(
this.session(),
new DependencyRequest(crq, filter)
);
}
/**
* Fetch dependencies.
*
* @param session The session
* @param dreq Dependency request
* @return The list of dependencies
* @throws org.sonatype.aether.resolution.DependencyResolutionException
* If can't fetch it
* @todo #51 This catch of NPE is a temporary measure. I don't know why
* Aether throws NPE in case of non-resolvable artifact. This is the best
* I can do at the moment in order to protect clients of the class.
*/
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private DependencyResult fetch(
final RepositorySystemSession session,
final DependencyRequest dreq) throws DependencyResolutionException {
synchronized (this.localRepo) {
return this.system
.resolveDependencies(session, dreq);
}
}
/**
* Create collect request.
*
* @param root The root to start with
* @return The request
*/
private CollectRequest request(final Dependency root) {
final CollectRequest request = new CollectRequest();
request.setRoot(root);
for (RemoteRepository repo : this.remotes) {
if (!repo.getProtocol().matches("https?|file|s3")) {
Logger.warn(
this,
"%s ignored (only S3, HTTP/S, and FILE are supported)",
repo
);
continue;
}
request.addRepository(repo);
}
return request;
}
/**
* Convert a list of repositories into a list of strings.
*
* @param repos The list of them
* @return The list of texts
*/
private static Collection<String> reps(
final Collection<RemoteRepository> repos) {
final Collection<String> texts = new ArrayList<String>(repos.size());
final StringBuilder text = new StringBuilder();
for (RemoteRepository repo : repos) {
final Authentication auth = repo.getAuthentication();
text.setLength(0);
text.append(repo.toString());
if (auth == null) {
text.append(" without authentication");
} else {
text.append(" with ").append(auth.toString());
}
texts.add(text.toString());
}
return texts;
}
/**
* Create RepositorySystemSession.
*
* @return The session
*/
private RepositorySystemSession session() {
final LocalRepository local = new LocalRepository(this.localRepo);
final MavenRepositorySystemSession session =
new MavenRepositorySystemSession();
session.setLocalRepositoryManager(
this.system.newLocalRepositoryManager(local)
);
session.setTransferListener(new LogTransferListener());
return session;
}
public VersionRangeResult getVersions(Artifact artifact) {
try {
VersionRangeRequest rangeRequest = new VersionRangeRequest();
rangeRequest.setArtifact(artifact);
for (RemoteRepository remote : remotes) {
rangeRequest.addRepository(remote);
}
return system.resolveVersionRange(session(), rangeRequest);
} catch (VersionRangeResolutionException e) {
throw Exceptions.runtime(e);
}
}
}