Coverage Report - com.jcabi.aether.Aether
 
Classes in this File Line Coverage Branch Coverage Complexity
Aether
80%
83/103
57%
23/40
2.75
Aether$AjcClosure1
100%
1/1
N/A
2.75
Aether$AjcClosure3
100%
1/1
N/A
2.75
 
 1  2
 /**
 2  
  * Copyright (c) 2012-2014, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.aether;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.aspects.Loggable;
 34  
 import com.jcabi.log.Logger;
 35  
 import java.io.File;
 36  
 import java.nio.file.Files;
 37  
 import java.nio.file.Path;
 38  
 import java.nio.file.Paths;
 39  
 import java.util.ArrayList;
 40  
 import java.util.Collection;
 41  
 import java.util.LinkedList;
 42  
 import java.util.List;
 43  
 import javax.validation.constraints.NotNull;
 44  
 import lombok.EqualsAndHashCode;
 45  
 import lombok.ToString;
 46  
 import org.apache.maven.project.MavenProject;
 47  
 import org.apache.maven.repository.internal.MavenRepositorySystemSession;
 48  
 import org.apache.maven.settings.Mirror;
 49  
 import org.apache.maven.settings.Settings;
 50  
 import org.apache.maven.settings.SettingsUtils;
 51  
 import org.apache.maven.settings.TrackableBase;
 52  
 import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
 53  
 import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
 54  
 import org.apache.maven.settings.building.SettingsBuilder;
 55  
 import org.apache.maven.settings.building.SettingsBuildingException;
 56  
 import org.apache.maven.settings.building.SettingsBuildingRequest;
 57  
 import org.apache.maven.settings.building.SettingsBuildingResult;
 58  
 import org.sonatype.aether.RepositorySystem;
 59  
 import org.sonatype.aether.RepositorySystemSession;
 60  
 import org.sonatype.aether.artifact.Artifact;
 61  
 import org.sonatype.aether.collection.CollectRequest;
 62  
 import org.sonatype.aether.graph.Dependency;
 63  
 import org.sonatype.aether.graph.DependencyFilter;
 64  
 import org.sonatype.aether.repository.Authentication;
 65  
 import org.sonatype.aether.repository.LocalRepository;
 66  
 import org.sonatype.aether.repository.RemoteRepository;
 67  
 import org.sonatype.aether.resolution.ArtifactResult;
 68  
 import org.sonatype.aether.resolution.DependencyRequest;
 69  
 import org.sonatype.aether.resolution.DependencyResolutionException;
 70  
 import org.sonatype.aether.resolution.DependencyResult;
 71  
 import org.sonatype.aether.util.filter.DependencyFilterUtils;
 72  
 import org.sonatype.aether.util.repository.DefaultMirrorSelector;
 73  
 
 74  
 /**
 75  
  * Resolver of dependencies for one artifact.
 76  
  *
 77  
  * <p>You need the following dependencies to have in classpath in order to
 78  
  * to work with this class:
 79  
  *
 80  
  * <pre>
 81  
  * org.sonatype.aether:aether-api:1.13.1
 82  
  * org.apache.maven:maven-core:3.0.3
 83  
  * </pre>
 84  
  *
 85  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 86  
  * @version $Id$
 87  
  * @since 0.1.6
 88  
  * @checkstyle ClassDataAbstractionCoupling (500 lines)
 89  
  * @checkstyle ClassFanOutComplexity (500 lines)
 90  
  * @see <a href="http://sonatype.github.com/sonatype-aether/apidocs/overview-tree.html">Aether 1.13.1 JavaDoc</a>
 91  
  * @see Classpath
 92  
  * @todo #20 This class should be @Immutable, but Proxy and Authentication
 93  
  *  parameters of Repository are not immutable. Let's create a new classes to
 94  
  *  encapsulate all necessary properties from them.
 95  
  */
 96  0
 @ToString
 97  512
 @EqualsAndHashCode(of = { "remotes", "lrepo" })
 98  
 @Loggable(Loggable.DEBUG)
 99  
 @SuppressWarnings("PMD.ExcessiveImports")
 100  
 public final class Aether {
 101  
 
 102  
     /**
 103  
      * Remote project repositories.
 104  
      */
 105  
     @Immutable.Array
 106  
     private final transient Repository[] remotes;
 107  
 
 108  
     /**
 109  
      * Location of lrepo repository.
 110  
      */
 111  
     private final transient File lrepo;
 112  
 
 113  
     /**
 114  
      * Repository system.
 115  
      */
 116  13
     private final transient RepositorySystem system =
 117  
         new RepositorySystemBuilder().build();
 118  
 
 119  
     /**
 120  
      * Public ctor, requires information about all remote repositories and one
 121  
      * lrepo.
 122  
      * @param prj The Maven project
 123  
      * @param repo Local repository location (directory path)
 124  
      */
 125  
     public Aether(@NotNull final MavenProject prj, @NotNull final File repo) {
 126  15
         this(prj.getRemoteProjectRepositories(), repo);
 127  13
     }
 128  
 
 129  
     /**
 130  
      * Public ctor, requires information about all remote repositories and one
 131  
      * lrepo.
 132  
      * @param repos Collection of remote repositories
 133  
      * @param repo Local repository location (directory path)
 134  
      * @since 0.8
 135  
      */
 136  
     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
 137  
     public Aether(@NotNull final Collection<RemoteRepository> repos,
 138  13
         @NotNull final File repo) {
 139  13
         final Collection<Repository> rlist = new LinkedList<Repository>();
 140  13
         for (final RemoteRepository remote : this.mrepos(repos)) {
 141  31
             rlist.add(new Repository(remote));
 142  31
         }
 143  13
         this.remotes = rlist.toArray(new Repository[repos.size()]);
 144  13
         this.lrepo = repo;
 145  13
     }
 146  
 
 147  
     /**
 148  
      * List of transitive dependencies of the artifact.
 149  
      * @param root The artifact to work with
 150  
      * @param scope The scope to work with ("runtime", "test", etc.)
 151  
      * @return The list of dependencies
 152  
      * @throws DependencyResolutionException If can't fetch it
 153  
      */
 154  
     public List<Artifact> resolve(@NotNull final Artifact root,
 155  
         @NotNull final String scope) throws DependencyResolutionException {
 156  74
         final DependencyFilter filter =
 157  
             DependencyFilterUtils.classpathFilter(scope);
 158  35
         if (filter == null) {
 159  0
             throw new IllegalStateException(
 160  
                 String.format("failed to create a filter for '%s'", scope)
 161  
             );
 162  
         }
 163  35
         return this.resolve(root, scope, filter);
 164  
     }
 165  
 
 166  
     /**
 167  
      * List of transitive dependencies of the artifact.
 168  
      * @param root The artifact to work with
 169  
      * @param scope The scope to work with ("runtime", "test", etc.)
 170  
      * @param filter The dependency filter to work with
 171  
      * @return The list of dependencies
 172  
      * @throws DependencyResolutionException If can't fetch it
 173  
      */
 174  
     public List<Artifact> resolve(@NotNull final Artifact root,
 175  
         @NotNull final String scope, @NotNull final DependencyFilter filter)
 176  
         throws DependencyResolutionException {
 177  84
         final Dependency rdep = new Dependency(root, scope);
 178  42
         final CollectRequest crq = this.request(rdep);
 179  42
         final List<Artifact> deps = new LinkedList<Artifact>();
 180  42
         deps.addAll(
 181  
             this.fetch(
 182  
                 this.session(),
 183  
                 new DependencyRequest(crq, filter)
 184  
             )
 185  
         );
 186  38
         return deps;
 187  
     }
 188  
 
 189  
     /**
 190  
      * Build repositories taking mirrors into consideration.
 191  
      * @param repos Initial list of repositories.
 192  
      * @return List of repositories with mirrored ones.
 193  
      */
 194  
     private Collection<RemoteRepository> mrepos(
 195  
         final Collection<RemoteRepository> repos) {
 196  13
         final DefaultMirrorSelector selector = this.mirror(this.settings());
 197  13
         final Collection<RemoteRepository> mrepos =
 198  
             new ArrayList<RemoteRepository>(repos.size());
 199  13
         for (final RemoteRepository repo : repos) {
 200  31
             final RemoteRepository mrepo = selector.getMirror(repo);
 201  31
             if (mrepo == null) {
 202  31
                 mrepos.add(repo);
 203  
             } else {
 204  0
                 mrepos.add(mrepo);
 205  
             }
 206  31
         }
 207  13
         return mrepos;
 208  
     }
 209  
 
 210  
     /**
 211  
      * Fetch dependencies.
 212  
      * Catch of NPE is required because sonatype even when it can't resolve
 213  
      * given artifact tries to get its root and execute a method on it,
 214  
      * which is not possible and results in NPE. Moreover sonatype library
 215  
      * is not developed since 2011 so this bug won't be fixed.
 216  
      * @param session The session
 217  
      * @param dreq Dependency request
 218  
      * @return The list of dependencies
 219  
      * @throws DependencyResolutionException If can't fetch it
 220  
      */
 221  
     @SuppressWarnings("PMD.AvoidCatchingGenericException")
 222  
     private List<Artifact> fetch(final RepositorySystemSession session,
 223  
         final DependencyRequest dreq) throws DependencyResolutionException {
 224  42
         final List<Artifact> deps = new LinkedList<Artifact>();
 225  
         try {
 226  
             Collection<ArtifactResult> results;
 227  42
             synchronized (this.lrepo) {
 228  42
                 results = this.system
 229  
                     .resolveDependencies(session, dreq)
 230  
                     .getArtifactResults();
 231  38
             }
 232  38
             for (final ArtifactResult res : results) {
 233  55
                 deps.add(res.getArtifact());
 234  55
             }
 235  
         // @checkstyle IllegalCatch (1 line)
 236  4
         } catch (final Exception ex) {
 237  4
             throw new DependencyResolutionException(
 238  
                 new DependencyResult(dreq),
 239  
                 new IllegalArgumentException(
 240  
                     Logger.format(
 241  
                         "failed to load '%s' from %[list]s into %s",
 242  
                         dreq.getCollectRequest().getRoot(),
 243  
                         Aether.reps(dreq.getCollectRequest().getRepositories()),
 244  
                         session.getLocalRepositoryManager()
 245  
                             .getRepository()
 246  
                             .getBasedir()
 247  
                     ),
 248  
                     ex
 249  
                 )
 250  
             );
 251  38
         }
 252  38
         return deps;
 253  
     }
 254  
 
 255  
     /**
 256  
      * Create collect request.
 257  
      * @param root The root to start with
 258  
      * @return The request
 259  
      */
 260  
     private CollectRequest request(final Dependency root) {
 261  42
         final CollectRequest request = new CollectRequest();
 262  42
         request.setRoot(root);
 263  189
         for (final Repository repo : this.remotes) {
 264  147
             final RemoteRepository remote = repo.remote();
 265  147
             if (!remote.getProtocol().matches("https?|file|s3")) {
 266  0
                 Logger.warn(
 267  
                     this,
 268  
                     "%s ignored (only S3, HTTP/S, and FILE are supported)",
 269  
                     repo
 270  
                 );
 271  0
                 continue;
 272  
             }
 273  147
             request.addRepository(remote);
 274  
         }
 275  42
         return request;
 276  
     }
 277  
 
 278  
     /**
 279  
      * Convert a list of repositories into a list of strings.
 280  
      * @param repos The list of them
 281  
      * @return The list of texts
 282  
      */
 283  
     private static Collection<String> reps(
 284  
         final Collection<RemoteRepository> repos) {
 285  4
         final Collection<String> texts = new ArrayList<String>(repos.size());
 286  4
         final StringBuilder text = new StringBuilder();
 287  4
         for (final RemoteRepository repo : repos) {
 288  10
             final Authentication auth = repo.getAuthentication();
 289  10
             text.setLength(0);
 290  10
             text.append(repo.toString());
 291  10
             if (auth == null) {
 292  10
                 text.append(" without authentication");
 293  
             } else {
 294  0
                 text.append(" with ").append(auth.toString());
 295  
             }
 296  10
             texts.add(text.toString());
 297  10
         }
 298  4
         return texts;
 299  
     }
 300  
 
 301  
     /**
 302  
      * Create RepositorySystemSession.
 303  
      * @return The session
 304  
      */
 305  
     private RepositorySystemSession session() {
 306  42
         final LocalRepository local = new LocalRepository(this.lrepo);
 307  42
         final MavenRepositorySystemSession session =
 308  
             new MavenRepositorySystemSession();
 309  42
         session.setLocalRepositoryManager(
 310  
             this.system.newLocalRepositoryManager(local)
 311  
         );
 312  42
         session.setTransferListener(new LogTransferListener());
 313  42
         return session;
 314  
     }
 315  
 
 316  
     /**
 317  
      * Setup mirrors based on maven settings.
 318  
      * @param settings Settings to use.
 319  
      * @return Mirror selector.
 320  
      */
 321  
     private DefaultMirrorSelector mirror(final Settings settings) {
 322  13
         final DefaultMirrorSelector selector =
 323  
             new DefaultMirrorSelector();
 324  13
         final List<Mirror> mirrors = settings.getMirrors();
 325  13
         Logger.warn(
 326  
             this,
 327  
             "mirrors: %s",
 328  
             mirrors
 329  
         );
 330  13
         if (mirrors != null) {
 331  13
             for (final Mirror mirror : mirrors) {
 332  0
                 selector.add(
 333  
                     mirror.getId(), mirror.getUrl(), mirror.getLayout(), false,
 334  
                     mirror.getMirrorOf(), mirror.getMirrorOfLayouts()
 335  
                 );
 336  0
             }
 337  
         }
 338  13
         return selector;
 339  
     }
 340  
 
 341  
     /**
 342  
      * Provide settings from maven.
 343  
      * @return Maven settings.
 344  
      */
 345  
     private Settings settings() {
 346  13
         final SettingsBuilder builder =
 347  
             new DefaultSettingsBuilderFactory().newInstance();
 348  13
         final SettingsBuildingRequest request =
 349  
             new DefaultSettingsBuildingRequest();
 350  13
         final String user =
 351  
             System.getProperty("org.apache.maven.user-settings");
 352  13
         if (user == null) {
 353  13
             request.setUserSettingsFile(
 354  
                 new File(
 355  
                     new File(
 356  
                         System.getProperty("user.home")
 357  
                     ).getAbsoluteFile(),
 358  
                     "/.m2/settings.xml"
 359  
                 )
 360  
             );
 361  
         } else {
 362  0
             request.setUserSettingsFile(new File(user));
 363  
         }
 364  13
         final String global =
 365  
             System.getProperty("org.apache.maven.global-settings");
 366  13
         if (global != null) {
 367  0
             request.setGlobalSettingsFile(new File(global));
 368  
         }
 369  
         final SettingsBuildingResult result;
 370  
         try {
 371  13
             result = builder.build(request);
 372  0
         } catch (final SettingsBuildingException ex) {
 373  0
             throw new IllegalStateException(ex);
 374  13
         }
 375  13
         return this.invokers(builder, result);
 376  
     }
 377  
 
 378  
     /**
 379  
      * Apply maven invoker settings.
 380  
      * @param builder Settings builder.
 381  
      * @param result User and global settings.
 382  
      * @return User, global and invoker settings.
 383  
      */
 384  
     private Settings invokers(final SettingsBuilder builder,
 385  
         final SettingsBuildingResult result) {
 386  13
         Settings main = result.getEffectiveSettings();
 387  13
         final Path path = Paths.get(
 388  
             System.getProperty("user.dir"), "..", "interpolated-settings.xml"
 389  
         );
 390  13
         if (Files.exists(path)) {
 391  0
             final DefaultSettingsBuildingRequest irequest =
 392  
                 new DefaultSettingsBuildingRequest();
 393  0
             irequest.setUserSettingsFile(path.toAbsolutePath().toFile());
 394  
             try {
 395  0
                 final Settings isettings = builder.build(irequest)
 396  
                     .getEffectiveSettings();
 397  0
                 SettingsUtils.merge(isettings, main, TrackableBase.USER_LEVEL);
 398  0
                 main = isettings;
 399  0
             } catch (final SettingsBuildingException ex) {
 400  0
                 throw new IllegalStateException(ex);
 401  0
             }
 402  
         }
 403  13
         return main;
 404  
     }
 405  
 }
 406