View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.artifact.resolver;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.LinkedHashMap;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.concurrent.CountDownLatch;
34  import java.util.concurrent.Executor;
35  import java.util.concurrent.ExecutorService;
36  import java.util.concurrent.LinkedBlockingQueue;
37  import java.util.concurrent.ThreadFactory;
38  import java.util.concurrent.ThreadPoolExecutor;
39  import java.util.concurrent.TimeUnit;
40  import java.util.concurrent.atomic.AtomicInteger;
41  import java.util.regex.Matcher;
42  
43  import org.apache.maven.RepositoryUtils;
44  import org.apache.maven.artifact.Artifact;
45  import org.apache.maven.artifact.factory.ArtifactFactory;
46  import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
47  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
48  import org.apache.maven.artifact.metadata.ResolutionGroup;
49  import org.apache.maven.artifact.repository.ArtifactRepository;
50  import org.apache.maven.artifact.repository.LegacyLocalRepositoryManager;
51  import org.apache.maven.artifact.repository.RepositoryRequest;
52  import org.apache.maven.artifact.repository.metadata.Snapshot;
53  import org.apache.maven.artifact.repository.metadata.SnapshotArtifactRepositoryMetadata;
54  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
55  import org.apache.maven.execution.MavenSession;
56  import org.apache.maven.plugin.LegacySupport;
57  import org.apache.maven.repository.legacy.metadata.DefaultMetadataResolutionRequest;
58  import org.apache.maven.repository.legacy.metadata.MetadataResolutionRequest;
59  import org.apache.maven.repository.legacy.resolver.conflict.ConflictResolver;
60  import org.apache.maven.wagon.events.TransferListener;
61  import org.codehaus.plexus.PlexusContainer;
62  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
63  import org.codehaus.plexus.logging.Logger;
64  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
65  import org.eclipse.aether.RepositorySystem;
66  import org.eclipse.aether.RepositorySystemSession;
67  import org.eclipse.aether.repository.LocalRepositoryManager;
68  import org.eclipse.aether.resolution.ArtifactRequest;
69  import org.eclipse.aether.resolution.ArtifactResult;
70  
71  /**
72   * @author Jason van Zyl
73   */
74  @Named
75  @Singleton
76  public class DefaultArtifactResolver implements ArtifactResolver, Disposable {
77      @Inject
78      private Logger logger;
79  
80      @Inject
81      protected ArtifactFactory artifactFactory;
82  
83      @Inject
84      private ArtifactCollector artifactCollector;
85  
86      @Inject
87      private ResolutionErrorHandler resolutionErrorHandler;
88  
89      @Inject
90      private ArtifactMetadataSource source;
91  
92      @Inject
93      private PlexusContainer container;
94  
95      @Inject
96      private LegacySupport legacySupport;
97  
98      private final Executor executor;
99  
100     public DefaultArtifactResolver() {
101         int threads = Integer.getInteger("maven.artifact.threads", 5);
102         if (threads <= 1) {
103             executor = Runnable::run;
104         } else {
105             executor = new ThreadPoolExecutor(
106                     threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DaemonThreadCreator());
107         }
108     }
109 
110     private RepositorySystemSession getSession(ArtifactRepository localRepository) {
111         return LegacyLocalRepositoryManager.overlay(localRepository, legacySupport.getRepositorySession(), null);
112     }
113 
114     private void injectSession1(RepositoryRequest request, MavenSession session) {
115         if (session != null) {
116             request.setOffline(session.isOffline());
117             request.setForceUpdate(session.getRequest().isUpdateSnapshots());
118         }
119     }
120 
121     private void injectSession2(ArtifactResolutionRequest request, MavenSession session) {
122         injectSession1(request, session);
123 
124         if (session != null) {
125             request.setServers(session.getRequest().getServers());
126             request.setMirrors(session.getRequest().getMirrors());
127             request.setProxies(session.getRequest().getProxies());
128         }
129     }
130 
131     public void resolve(
132             Artifact artifact,
133             List<ArtifactRepository> remoteRepositories,
134             ArtifactRepository localRepository,
135             TransferListener resolutionListener)
136             throws ArtifactResolutionException, ArtifactNotFoundException {
137         resolve(artifact, remoteRepositories, getSession(localRepository));
138     }
139 
140     public void resolveAlways(
141             Artifact artifact, List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository)
142             throws ArtifactResolutionException, ArtifactNotFoundException {
143         resolve(artifact, remoteRepositories, getSession(localRepository));
144     }
145 
146     private void resolve(
147             Artifact artifact, List<ArtifactRepository> remoteRepositories, RepositorySystemSession session)
148             throws ArtifactResolutionException, ArtifactNotFoundException {
149         if (artifact == null) {
150             return;
151         }
152 
153         if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
154             File systemFile = artifact.getFile();
155 
156             if (systemFile == null) {
157                 throw new ArtifactNotFoundException("System artifact: " + artifact + " has no file attached", artifact);
158             }
159 
160             if (!systemFile.exists()) {
161                 throw new ArtifactNotFoundException(
162                         "System artifact: " + artifact + " not found in path: " + systemFile, artifact);
163             }
164 
165             if (!systemFile.isFile()) {
166                 throw new ArtifactNotFoundException(
167                         "System artifact: " + artifact + " is not a file: " + systemFile, artifact);
168             }
169 
170             artifact.setResolved(true);
171 
172             return;
173         }
174 
175         if (!artifact.isResolved()) {
176             ArtifactResult result;
177 
178             try {
179                 ArtifactRequest artifactRequest = new ArtifactRequest();
180                 artifactRequest.setArtifact(RepositoryUtils.toArtifact(artifact));
181                 artifactRequest.setRepositories(RepositoryUtils.toRepos(remoteRepositories));
182 
183                 // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
184                 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
185                 String path = lrm.getPathForLocalArtifact(artifactRequest.getArtifact());
186                 artifact.setFile(new File(lrm.getRepository().getBasedir(), path));
187 
188                 RepositorySystem repoSystem = container.lookup(RepositorySystem.class);
189                 result = repoSystem.resolveArtifact(session, artifactRequest);
190             } catch (ComponentLookupException e) {
191                 throw new IllegalStateException("Unable to lookup " + RepositorySystem.class.getName());
192             } catch (org.eclipse.aether.resolution.ArtifactResolutionException e) {
193                 if (e.getCause() instanceof org.eclipse.aether.transfer.ArtifactNotFoundException) {
194                     throw new ArtifactNotFoundException(e.getMessage(), artifact, remoteRepositories, e);
195                 } else {
196                     throw new ArtifactResolutionException(e.getMessage(), artifact, remoteRepositories, e);
197                 }
198             }
199 
200             artifact.selectVersion(result.getArtifact().getVersion());
201             artifact.setFile(result.getArtifact().getFile());
202             artifact.setResolved(true);
203 
204             if (artifact.isSnapshot()) {
205                 Matcher matcher = Artifact.VERSION_FILE_PATTERN.matcher(artifact.getVersion());
206                 if (matcher.matches()) {
207                     Snapshot snapshot = new Snapshot();
208                     snapshot.setTimestamp(matcher.group(2));
209                     try {
210                         snapshot.setBuildNumber(Integer.parseInt(matcher.group(3)));
211                         artifact.addMetadata(new SnapshotArtifactRepositoryMetadata(artifact, snapshot));
212                     } catch (NumberFormatException e) {
213                         logger.warn("Invalid artifact version " + artifact.getVersion() + ": " + e.getMessage());
214                     }
215                 }
216             }
217         }
218     }
219 
220     public ArtifactResolutionResult resolveTransitively(
221             Set<Artifact> artifacts,
222             Artifact originatingArtifact,
223             ArtifactRepository localRepository,
224             List<ArtifactRepository> remoteRepositories,
225             ArtifactMetadataSource source,
226             ArtifactFilter filter)
227             throws ArtifactResolutionException, ArtifactNotFoundException {
228         return resolveTransitively(
229                 artifacts,
230                 originatingArtifact,
231                 Collections.emptyMap(),
232                 localRepository,
233                 remoteRepositories,
234                 source,
235                 filter);
236     }
237 
238     public ArtifactResolutionResult resolveTransitively(
239             Set<Artifact> artifacts,
240             Artifact originatingArtifact,
241             Map<String, Artifact> managedVersions,
242             ArtifactRepository localRepository,
243             List<ArtifactRepository> remoteRepositories,
244             ArtifactMetadataSource source)
245             throws ArtifactResolutionException, ArtifactNotFoundException {
246         return resolveTransitively(
247                 artifacts, originatingArtifact, managedVersions, localRepository, remoteRepositories, source, null);
248     }
249 
250     public ArtifactResolutionResult resolveTransitively(
251             Set<Artifact> artifacts,
252             Artifact originatingArtifact,
253             Map<String, Artifact> managedVersions,
254             ArtifactRepository localRepository,
255             List<ArtifactRepository> remoteRepositories,
256             ArtifactMetadataSource source,
257             ArtifactFilter filter)
258             throws ArtifactResolutionException, ArtifactNotFoundException {
259         return resolveTransitively(
260                 artifacts,
261                 originatingArtifact,
262                 managedVersions,
263                 localRepository,
264                 remoteRepositories,
265                 source,
266                 filter,
267                 null);
268     }
269 
270     public ArtifactResolutionResult resolveTransitively(
271             Set<Artifact> artifacts,
272             Artifact originatingArtifact,
273             List<ArtifactRepository> remoteRepositories,
274             ArtifactRepository localRepository,
275             ArtifactMetadataSource source)
276             throws ArtifactResolutionException, ArtifactNotFoundException {
277         return resolveTransitively(artifacts, originatingArtifact, localRepository, remoteRepositories, source, null);
278     }
279 
280     public ArtifactResolutionResult resolveTransitively(
281             Set<Artifact> artifacts,
282             Artifact originatingArtifact,
283             List<ArtifactRepository> remoteRepositories,
284             ArtifactRepository localRepository,
285             ArtifactMetadataSource source,
286             List<ResolutionListener> listeners)
287             throws ArtifactResolutionException, ArtifactNotFoundException {
288         return resolveTransitively(
289                 artifacts,
290                 originatingArtifact,
291                 Collections.emptyMap(),
292                 localRepository,
293                 remoteRepositories,
294                 source,
295                 null,
296                 listeners);
297     }
298 
299     @SuppressWarnings("checkstyle:parameternumber")
300     public ArtifactResolutionResult resolveTransitively(
301             Set<Artifact> artifacts,
302             Artifact originatingArtifact,
303             Map<String, Artifact> managedVersions,
304             ArtifactRepository localRepository,
305             List<ArtifactRepository> remoteRepositories,
306             ArtifactMetadataSource source,
307             ArtifactFilter filter,
308             List<ResolutionListener> listeners)
309             throws ArtifactResolutionException, ArtifactNotFoundException {
310         return resolveTransitively(
311                 artifacts,
312                 originatingArtifact,
313                 managedVersions,
314                 localRepository,
315                 remoteRepositories,
316                 source,
317                 filter,
318                 listeners,
319                 null);
320     }
321 
322     @SuppressWarnings("checkstyle:parameternumber")
323     public ArtifactResolutionResult resolveTransitively(
324             Set<Artifact> artifacts,
325             Artifact originatingArtifact,
326             Map<String, Artifact> managedVersions,
327             ArtifactRepository localRepository,
328             List<ArtifactRepository> remoteRepositories,
329             ArtifactMetadataSource source,
330             ArtifactFilter filter,
331             List<ResolutionListener> listeners,
332             List<ConflictResolver> conflictResolvers)
333             throws ArtifactResolutionException, ArtifactNotFoundException {
334         ArtifactResolutionRequest request = new ArtifactResolutionRequest()
335                 .setArtifact(originatingArtifact)
336                 .setResolveRoot(false)
337                 .
338                 // This is required by the surefire plugin
339                 setArtifactDependencies(artifacts)
340                 .setManagedVersionMap(managedVersions)
341                 .setLocalRepository(localRepository)
342                 .setRemoteRepositories(remoteRepositories)
343                 .setCollectionFilter(filter)
344                 .setListeners(listeners);
345 
346         injectSession2(request, legacySupport.getSession());
347 
348         return resolveWithExceptions(request);
349     }
350 
351     public ArtifactResolutionResult resolveWithExceptions(ArtifactResolutionRequest request)
352             throws ArtifactResolutionException, ArtifactNotFoundException {
353         ArtifactResolutionResult result = resolve(request);
354 
355         // We have collected all the problems so let's mimic the way the old code worked and just blow up right here.
356         // That's right lets just let it rip right here and send a big incomprehensible blob of text at unsuspecting
357         // users. Bad dog!
358 
359         resolutionErrorHandler.throwErrors(request, result);
360 
361         return result;
362     }
363 
364     // ------------------------------------------------------------------------
365     //
366     // ------------------------------------------------------------------------
367 
368     @SuppressWarnings("checkstyle:methodlength")
369     public ArtifactResolutionResult resolve(ArtifactResolutionRequest request) {
370         Artifact rootArtifact = request.getArtifact();
371         Set<Artifact> artifacts = request.getArtifactDependencies();
372         Map<String, Artifact> managedVersions = request.getManagedVersionMap();
373         List<ResolutionListener> listeners = request.getListeners();
374         ArtifactFilter collectionFilter = request.getCollectionFilter();
375         ArtifactFilter resolutionFilter = request.getResolutionFilter();
376         RepositorySystemSession session = getSession(request.getLocalRepository());
377 
378         // TODO: hack because metadata isn't generated in m2e correctly and i want to run the maven i have in the
379         // workspace
380         if (source == null) {
381             try {
382                 source = container.lookup(ArtifactMetadataSource.class);
383             } catch (ComponentLookupException e) {
384                 // won't happen
385             }
386         }
387 
388         if (listeners == null) {
389             listeners = new ArrayList<>();
390 
391             if (logger.isDebugEnabled()) {
392                 listeners.add(new DebugResolutionListener(logger));
393             }
394 
395             listeners.add(new WarningResolutionListener(logger));
396         }
397 
398         ArtifactResolutionResult result = new ArtifactResolutionResult();
399 
400         // The root artifact may, or may not be resolved so we need to check before we attempt to resolve.
401         // This is often an artifact like a POM that is taken from disk and we already have hold of the
402         // file reference. But this may be a Maven Plugin that we need to resolve from a remote repository
403         // as well as its dependencies.
404 
405         if (request.isResolveRoot() /* && rootArtifact.getFile() == null */) {
406             try {
407                 resolve(rootArtifact, request.getRemoteRepositories(), session);
408             } catch (ArtifactResolutionException e) {
409                 result.addErrorArtifactException(e);
410                 return result;
411             } catch (ArtifactNotFoundException e) {
412                 result.addMissingArtifact(request.getArtifact());
413                 return result;
414             }
415         }
416 
417         ArtifactResolutionRequest collectionRequest = request;
418 
419         if (request.isResolveTransitively()) {
420             MetadataResolutionRequest metadataRequest = new DefaultMetadataResolutionRequest(request);
421 
422             metadataRequest.setArtifact(rootArtifact);
423             metadataRequest.setResolveManagedVersions(managedVersions == null);
424 
425             try {
426                 ResolutionGroup resolutionGroup = source.retrieve(metadataRequest);
427 
428                 if (managedVersions == null) {
429                     managedVersions = resolutionGroup.getManagedVersions();
430                 }
431 
432                 Set<Artifact> directArtifacts = resolutionGroup.getArtifacts();
433 
434                 if (artifacts == null || artifacts.isEmpty()) {
435                     artifacts = directArtifacts;
436                 } else {
437                     List<Artifact> allArtifacts = new ArrayList<>();
438                     allArtifacts.addAll(artifacts);
439                     allArtifacts.addAll(directArtifacts);
440 
441                     Map<String, Artifact> mergedArtifacts = new LinkedHashMap<>();
442                     for (Artifact artifact : allArtifacts) {
443                         String conflictId = artifact.getDependencyConflictId();
444                         if (!mergedArtifacts.containsKey(conflictId)) {
445                             mergedArtifacts.put(conflictId, artifact);
446                         }
447                     }
448 
449                     artifacts = new LinkedHashSet<>(mergedArtifacts.values());
450                 }
451 
452                 collectionRequest = new ArtifactResolutionRequest(request);
453                 collectionRequest.setServers(request.getServers());
454                 collectionRequest.setMirrors(request.getMirrors());
455                 collectionRequest.setProxies(request.getProxies());
456                 collectionRequest.setRemoteRepositories(resolutionGroup.getResolutionRepositories());
457             } catch (ArtifactMetadataRetrievalException e) {
458                 ArtifactResolutionException are = new ArtifactResolutionException(
459                         "Unable to get dependency information for " + rootArtifact.getId() + ": " + e.getMessage(),
460                         rootArtifact,
461                         metadataRequest.getRemoteRepositories(),
462                         e);
463                 result.addMetadataResolutionException(are);
464                 return result;
465             }
466         }
467 
468         if (artifacts == null || artifacts.isEmpty()) {
469             if (request.isResolveRoot()) {
470                 result.addArtifact(rootArtifact);
471             }
472             return result;
473         }
474 
475         // After the collection we will have the artifact object in the result but they will not be resolved yet.
476         result = artifactCollector.collect(
477                 artifacts, rootArtifact, managedVersions, collectionRequest, source, collectionFilter, listeners, null);
478 
479         // We have metadata retrieval problems, or there are cycles that have been detected
480         // so we give this back to the calling code and let them deal with this information
481         // appropriately.
482 
483         if (result.hasMetadataResolutionExceptions()
484                 || result.hasVersionRangeViolations()
485                 || result.hasCircularDependencyExceptions()) {
486             return result;
487         }
488 
489         if (result.getArtifactResolutionNodes() != null) {
490             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
491 
492             CountDownLatch latch =
493                     new CountDownLatch(result.getArtifactResolutionNodes().size());
494 
495             for (ResolutionNode node : result.getArtifactResolutionNodes()) {
496                 Artifact artifact = node.getArtifact();
497 
498                 if (resolutionFilter == null || resolutionFilter.include(artifact)) {
499                     executor.execute(new ResolveTask(
500                             classLoader, latch, artifact, session, node.getRemoteRepositories(), result));
501                 } else {
502                     latch.countDown();
503                 }
504             }
505             try {
506                 latch.await();
507             } catch (InterruptedException e) {
508                 result.addErrorArtifactException(
509                         new ArtifactResolutionException("Resolution interrupted", rootArtifact, e));
510             }
511         }
512 
513         // We want to send the root artifact back in the result but we need to do this after the other dependencies
514         // have been resolved.
515         if (request.isResolveRoot()) {
516             // Add the root artifact (as the first artifact to retain logical order of class path!)
517             Set<Artifact> allArtifacts = new LinkedHashSet<>();
518             allArtifacts.add(rootArtifact);
519             allArtifacts.addAll(result.getArtifacts());
520             result.setArtifacts(allArtifacts);
521         }
522 
523         return result;
524     }
525 
526     public void resolve(
527             Artifact artifact, List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository)
528             throws ArtifactResolutionException, ArtifactNotFoundException {
529         resolve(artifact, remoteRepositories, localRepository, null);
530     }
531 
532     /**
533      * ThreadCreator for creating daemon threads with fixed ThreadGroup-name.
534      */
535     static final class DaemonThreadCreator implements ThreadFactory {
536         static final String THREADGROUP_NAME = "org.apache.maven.artifact.resolver.DefaultArtifactResolver";
537 
538         static final ThreadGroup GROUP = new ThreadGroup(THREADGROUP_NAME);
539 
540         static final AtomicInteger THREAD_NUMBER = new AtomicInteger(1);
541 
542         public Thread newThread(Runnable r) {
543             Thread newThread = new Thread(GROUP, r, "resolver-" + THREAD_NUMBER.getAndIncrement());
544             newThread.setDaemon(true);
545             newThread.setContextClassLoader(null);
546             return newThread;
547         }
548     }
549 
550     private class ResolveTask implements Runnable {
551 
552         private final ClassLoader classLoader;
553 
554         private final CountDownLatch latch;
555 
556         private final Artifact artifact;
557 
558         private final RepositorySystemSession session;
559 
560         private final List<ArtifactRepository> remoteRepositories;
561 
562         private final ArtifactResolutionResult result;
563 
564         ResolveTask(
565                 ClassLoader classLoader,
566                 CountDownLatch latch,
567                 Artifact artifact,
568                 RepositorySystemSession session,
569                 List<ArtifactRepository> remoteRepositories,
570                 ArtifactResolutionResult result) {
571             this.classLoader = classLoader;
572             this.latch = latch;
573             this.artifact = artifact;
574             this.session = session;
575             this.remoteRepositories = remoteRepositories;
576             this.result = result;
577         }
578 
579         public void run() {
580             ClassLoader old = Thread.currentThread().getContextClassLoader();
581             try {
582                 Thread.currentThread().setContextClassLoader(classLoader);
583                 resolve(artifact, remoteRepositories, session);
584             } catch (ArtifactNotFoundException anfe) {
585                 // These are cases where the artifact just isn't present in any of the remote repositories
586                 // because it wasn't deployed, or it was deployed in the wrong place.
587 
588                 synchronized (result) {
589                     result.addMissingArtifact(artifact);
590                 }
591             } catch (ArtifactResolutionException e) {
592                 // This is really a wagon TransferFailedException so something went wrong after we successfully
593                 // retrieved the metadata.
594 
595                 synchronized (result) {
596                     result.addErrorArtifactException(e);
597                 }
598             } finally {
599                 latch.countDown();
600                 Thread.currentThread().setContextClassLoader(old);
601             }
602         }
603     }
604 
605     @Override
606     public void dispose() {
607         if (executor instanceof ExecutorService) {
608             ((ExecutorService) executor).shutdownNow();
609         }
610     }
611 }