001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.impl;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023import javax.inject.Singleton;
024
025import java.io.File;
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.concurrent.atomic.AtomicBoolean;
034
035import org.eclipse.aether.RepositoryEvent;
036import org.eclipse.aether.RepositoryEvent.EventType;
037import org.eclipse.aether.RepositorySystemSession;
038import org.eclipse.aether.RequestTrace;
039import org.eclipse.aether.SyncContext;
040import org.eclipse.aether.artifact.Artifact;
041import org.eclipse.aether.artifact.ArtifactProperties;
042import org.eclipse.aether.impl.ArtifactResolver;
043import org.eclipse.aether.impl.OfflineController;
044import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
045import org.eclipse.aether.impl.RemoteRepositoryManager;
046import org.eclipse.aether.impl.RepositoryConnectorProvider;
047import org.eclipse.aether.impl.RepositoryEventDispatcher;
048import org.eclipse.aether.impl.UpdateCheck;
049import org.eclipse.aether.impl.UpdateCheckManager;
050import org.eclipse.aether.impl.VersionResolver;
051import org.eclipse.aether.repository.ArtifactRepository;
052import org.eclipse.aether.repository.LocalArtifactRegistration;
053import org.eclipse.aether.repository.LocalArtifactRequest;
054import org.eclipse.aether.repository.LocalArtifactResult;
055import org.eclipse.aether.repository.LocalRepository;
056import org.eclipse.aether.repository.LocalRepositoryManager;
057import org.eclipse.aether.repository.RemoteRepository;
058import org.eclipse.aether.repository.RepositoryPolicy;
059import org.eclipse.aether.repository.WorkspaceReader;
060import org.eclipse.aether.resolution.ArtifactRequest;
061import org.eclipse.aether.resolution.ArtifactResolutionException;
062import org.eclipse.aether.resolution.ArtifactResult;
063import org.eclipse.aether.resolution.ResolutionErrorPolicy;
064import org.eclipse.aether.resolution.VersionRequest;
065import org.eclipse.aether.resolution.VersionResolutionException;
066import org.eclipse.aether.resolution.VersionResult;
067import org.eclipse.aether.spi.connector.ArtifactDownload;
068import org.eclipse.aether.spi.connector.RepositoryConnector;
069import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
070import org.eclipse.aether.spi.io.FileProcessor;
071import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
072import org.eclipse.aether.spi.synccontext.SyncContextFactory;
073import org.eclipse.aether.transfer.ArtifactFilteredOutException;
074import org.eclipse.aether.transfer.ArtifactNotFoundException;
075import org.eclipse.aether.transfer.ArtifactTransferException;
076import org.eclipse.aether.transfer.NoRepositoryConnectorException;
077import org.eclipse.aether.transfer.RepositoryOfflineException;
078import org.eclipse.aether.util.ConfigUtils;
079import org.slf4j.Logger;
080import org.slf4j.LoggerFactory;
081
082import static java.util.Objects.requireNonNull;
083
084/**
085 *
086 */
087@Singleton
088@Named
089public class DefaultArtifactResolver implements ArtifactResolver {
090
091    /**
092     * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names
093     * will have file names converted back to baseVersion. Default: {@code true}.
094     */
095    private static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = "aether.artifactResolver.snapshotNormalization";
096
097    /**
098     * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration
099     * is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used.
100     * Default: {@code false}.
101     */
102    private static final String CONFIG_PROP_SIMPLE_LRM_INTEROP = "aether.artifactResolver.simpleLrmInterop";
103
104    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactResolver.class);
105
106    private final FileProcessor fileProcessor;
107
108    private final RepositoryEventDispatcher repositoryEventDispatcher;
109
110    private final VersionResolver versionResolver;
111
112    private final UpdateCheckManager updateCheckManager;
113
114    private final RepositoryConnectorProvider repositoryConnectorProvider;
115
116    private final RemoteRepositoryManager remoteRepositoryManager;
117
118    private final SyncContextFactory syncContextFactory;
119
120    private final OfflineController offlineController;
121
122    private final Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors;
123
124    private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
125
126    @SuppressWarnings("checkstyle:parameternumber")
127    @Inject
128    public DefaultArtifactResolver(
129            FileProcessor fileProcessor,
130            RepositoryEventDispatcher repositoryEventDispatcher,
131            VersionResolver versionResolver,
132            UpdateCheckManager updateCheckManager,
133            RepositoryConnectorProvider repositoryConnectorProvider,
134            RemoteRepositoryManager remoteRepositoryManager,
135            SyncContextFactory syncContextFactory,
136            OfflineController offlineController,
137            Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors,
138            RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
139        this.fileProcessor = requireNonNull(fileProcessor, "file processor cannot be null");
140        this.repositoryEventDispatcher =
141                requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
142        this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
143        this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
144        this.repositoryConnectorProvider =
145                requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
146        this.remoteRepositoryManager =
147                requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
148        this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
149        this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
150        this.artifactResolverPostProcessors =
151                requireNonNull(artifactResolverPostProcessors, "artifact resolver post-processors cannot be null");
152        this.remoteRepositoryFilterManager =
153                requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
154    }
155
156    @Override
157    public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
158            throws ArtifactResolutionException {
159        requireNonNull(session, "session cannot be null");
160        requireNonNull(request, "request cannot be null");
161
162        return resolveArtifacts(session, Collections.singleton(request)).get(0);
163    }
164
165    @Override
166    public List<ArtifactResult> resolveArtifacts(
167            RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
168            throws ArtifactResolutionException {
169        requireNonNull(session, "session cannot be null");
170        requireNonNull(requests, "requests cannot be null");
171        try (SyncContext shared = syncContextFactory.newInstance(session, true);
172                SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
173            Collection<Artifact> artifacts = new ArrayList<>(requests.size());
174            for (ArtifactRequest request : requests) {
175                if (request.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, null) != null) {
176                    continue;
177                }
178                artifacts.add(request.getArtifact());
179            }
180
181            return resolve(shared, exclusive, artifacts, session, requests);
182        }
183    }
184
185    @SuppressWarnings("checkstyle:methodlength")
186    private List<ArtifactResult> resolve(
187            SyncContext shared,
188            SyncContext exclusive,
189            Collection<Artifact> subjects,
190            RepositorySystemSession session,
191            Collection<? extends ArtifactRequest> requests)
192            throws ArtifactResolutionException {
193        SyncContext current = shared;
194        try {
195            while (true) {
196                current.acquire(subjects, null);
197
198                boolean failures = false;
199                final List<ArtifactResult> results = new ArrayList<>(requests.size());
200                final boolean simpleLrmInterop = ConfigUtils.getBoolean(session, false, CONFIG_PROP_SIMPLE_LRM_INTEROP);
201                final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
202                final WorkspaceReader workspace = session.getWorkspaceReader();
203                final List<ResolutionGroup> groups = new ArrayList<>();
204                // filter != null: means "filtering applied", if null no filtering applied (behave as before)
205                final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
206
207                for (ArtifactRequest request : requests) {
208                    RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
209
210                    ArtifactResult result = new ArtifactResult(request);
211                    results.add(result);
212
213                    Artifact artifact = request.getArtifact();
214
215                    if (current == shared) {
216                        artifactResolving(session, trace, artifact);
217                    }
218
219                    String localPath = artifact.getProperty(ArtifactProperties.LOCAL_PATH, null);
220                    if (localPath != null) {
221                        // unhosted artifact, just validate file
222                        File file = new File(localPath);
223                        if (!file.isFile()) {
224                            failures = true;
225                            result.addException(new ArtifactNotFoundException(artifact, null));
226                        } else {
227                            artifact = artifact.setFile(file);
228                            result.setArtifact(artifact);
229                            artifactResolved(session, trace, artifact, null, result.getExceptions());
230                        }
231                        continue;
232                    }
233
234                    List<RemoteRepository> remoteRepositories = request.getRepositories();
235                    List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
236                    if (filter != null) {
237                        for (RemoteRepository repository : remoteRepositories) {
238                            RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
239                            if (!filterResult.isAccepted()) {
240                                result.addException(new ArtifactFilteredOutException(
241                                        artifact, repository, filterResult.reasoning()));
242                                filteredRemoteRepositories.remove(repository);
243                            }
244                        }
245                    }
246
247                    VersionResult versionResult;
248                    try {
249                        VersionRequest versionRequest =
250                                new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
251                        versionRequest.setTrace(trace);
252                        versionResult = versionResolver.resolveVersion(session, versionRequest);
253                    } catch (VersionResolutionException e) {
254                        result.addException(e);
255                        continue;
256                    }
257
258                    artifact = artifact.setVersion(versionResult.getVersion());
259
260                    if (versionResult.getRepository() != null) {
261                        if (versionResult.getRepository() instanceof RemoteRepository) {
262                            filteredRemoteRepositories =
263                                    Collections.singletonList((RemoteRepository) versionResult.getRepository());
264                        } else {
265                            filteredRemoteRepositories = Collections.emptyList();
266                        }
267                    }
268
269                    if (workspace != null) {
270                        File file = workspace.findArtifact(artifact);
271                        if (file != null) {
272                            artifact = artifact.setFile(file);
273                            result.setArtifact(artifact);
274                            result.setRepository(workspace.getRepository());
275                            artifactResolved(session, trace, artifact, result.getRepository(), null);
276                            continue;
277                        }
278                    }
279
280                    LocalArtifactResult local = lrm.find(
281                            session,
282                            new LocalArtifactRequest(
283                                    artifact, filteredRemoteRepositories, request.getRequestContext()));
284                    result.setLocalArtifactResult(local);
285                    boolean found = (filter != null && local.isAvailable()) || isLocallyInstalled(local, versionResult);
286                    // with filtering it is availability that drives logic
287                    // without filtering it is simply presence of file that drives the logic
288                    // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
289                    if (found) {
290                        if (local.getRepository() != null) {
291                            result.setRepository(local.getRepository());
292                        } else {
293                            result.setRepository(lrm.getRepository());
294                        }
295
296                        try {
297                            artifact = artifact.setFile(getFile(session, artifact, local.getFile()));
298                            result.setArtifact(artifact);
299                            artifactResolved(session, trace, artifact, result.getRepository(), null);
300                        } catch (ArtifactTransferException e) {
301                            result.addException(e);
302                        }
303                        if (filter == null && simpleLrmInterop && !local.isAvailable()) {
304                            /*
305                             * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
306                             * manager will not show up in the repository tracking file of the enhanced local repository.
307                             * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
308                             * sync the repository tracking file.
309                             */
310                            lrm.add(session, new LocalArtifactRegistration(artifact));
311                        }
312
313                        continue;
314                    }
315
316                    if (local.getFile() != null) {
317                        LOGGER.info(
318                                "Artifact {} is present in the local repository, but cached from a remote repository ID that is unavailable in current build context, verifying that is downloadable from {}",
319                                artifact,
320                                remoteRepositories);
321                    }
322
323                    LOGGER.debug("Resolving artifact {} from {}", artifact, remoteRepositories);
324                    AtomicBoolean resolved = new AtomicBoolean(false);
325                    Iterator<ResolutionGroup> groupIt = groups.iterator();
326                    for (RemoteRepository repo : filteredRemoteRepositories) {
327                        if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
328                            continue;
329                        }
330
331                        try {
332                            Utils.checkOffline(session, offlineController, repo);
333                        } catch (RepositoryOfflineException e) {
334                            Exception exception = new ArtifactNotFoundException(
335                                    artifact,
336                                    repo,
337                                    "Cannot access " + repo.getId() + " ("
338                                            + repo.getUrl() + ") in offline mode and the artifact " + artifact
339                                            + " has not been downloaded from it before.",
340                                    e);
341                            result.addException(exception);
342                            continue;
343                        }
344
345                        ResolutionGroup group = null;
346                        while (groupIt.hasNext()) {
347                            ResolutionGroup t = groupIt.next();
348                            if (t.matches(repo)) {
349                                group = t;
350                                break;
351                            }
352                        }
353                        if (group == null) {
354                            group = new ResolutionGroup(repo);
355                            groups.add(group);
356                            groupIt = Collections.emptyIterator();
357                        }
358                        group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
359                    }
360                }
361
362                if (!groups.isEmpty() && current == shared) {
363                    current.close();
364                    current = exclusive;
365                    continue;
366                }
367
368                for (ResolutionGroup group : groups) {
369                    performDownloads(session, group);
370                }
371
372                for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
373                        artifactResolverPostProcessors.values()) {
374                    artifactResolverPostProcessor.postProcess(session, results);
375                }
376
377                for (ArtifactResult result : results) {
378                    ArtifactRequest request = result.getRequest();
379
380                    Artifact artifact = result.getArtifact();
381                    if (artifact == null || artifact.getFile() == null) {
382                        failures = true;
383                        if (result.getExceptions().isEmpty()) {
384                            Exception exception = new ArtifactNotFoundException(request.getArtifact(), null);
385                            result.addException(exception);
386                        }
387                        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
388                        artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
389                    }
390                }
391
392                if (failures) {
393                    throw new ArtifactResolutionException(results);
394                }
395
396                return results;
397            }
398        } finally {
399            current.close();
400        }
401    }
402
403    private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
404        if (lar.isAvailable()) {
405            return true;
406        }
407        if (lar.getFile() != null) {
408            // resolution of version range found locally installed artifact
409            if (vr.getRepository() instanceof LocalRepository) {
410                // resolution of (snapshot) version found locally installed artifact
411                return true;
412            } else {
413                return vr.getRepository() == null
414                        && lar.getRequest().getRepositories().isEmpty();
415            }
416        }
417        return false;
418    }
419
420    private File getFile(RepositorySystemSession session, Artifact artifact, File file)
421            throws ArtifactTransferException {
422        if (artifact.isSnapshot()
423                && !artifact.getVersion().equals(artifact.getBaseVersion())
424                && ConfigUtils.getBoolean(session, true, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
425            String name = file.getName().replace(artifact.getVersion(), artifact.getBaseVersion());
426            File dst = new File(file.getParent(), name);
427
428            boolean copy = dst.length() != file.length() || dst.lastModified() != file.lastModified();
429            if (copy) {
430                try {
431                    fileProcessor.copy(file, dst);
432                    dst.setLastModified(file.lastModified());
433                } catch (IOException e) {
434                    throw new ArtifactTransferException(artifact, null, e);
435                }
436            }
437
438            file = dst;
439        }
440
441        return file;
442    }
443
444    private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
445        List<ArtifactDownload> downloads = gatherDownloads(session, group);
446        if (downloads.isEmpty()) {
447            return;
448        }
449
450        for (ArtifactDownload download : downloads) {
451            artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
452        }
453
454        try {
455            try (RepositoryConnector connector =
456                    repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
457                connector.get(downloads, null);
458            }
459        } catch (NoRepositoryConnectorException e) {
460            for (ArtifactDownload download : downloads) {
461                download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
462            }
463        }
464
465        evaluateDownloads(session, group);
466    }
467
468    private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
469        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
470        List<ArtifactDownload> downloads = new ArrayList<>();
471
472        for (ResolutionItem item : group.items) {
473            Artifact artifact = item.artifact;
474
475            if (item.resolved.get()) {
476                // resolved in previous resolution group
477                continue;
478            }
479
480            ArtifactDownload download = new ArtifactDownload();
481            download.setArtifact(artifact);
482            download.setRequestContext(item.request.getRequestContext());
483            download.setListener(SafeTransferListener.wrap(session));
484            download.setTrace(item.trace);
485            if (item.local.getFile() != null) {
486                download.setFile(item.local.getFile());
487                download.setExistenceCheck(true);
488            } else {
489                String path =
490                        lrm.getPathForRemoteArtifact(artifact, group.repository, item.request.getRequestContext());
491                download.setFile(new File(lrm.getRepository().getBasedir(), path));
492            }
493
494            boolean snapshot = artifact.isSnapshot();
495            RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
496
497            int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
498            if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
499                UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
500                check.setItem(artifact);
501                check.setFile(download.getFile());
502                check.setFileValid(false);
503                check.setRepository(group.repository);
504                check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
505                check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
506                item.updateCheck = check;
507                updateCheckManager.checkArtifact(session, check);
508                if (!check.isRequired()) {
509                    item.result.addException(check.getException());
510                    continue;
511                }
512            }
513
514            download.setChecksumPolicy(policy.getChecksumPolicy());
515            download.setRepositories(item.repository.getMirroredRepositories());
516            downloads.add(download);
517            item.download = download;
518        }
519
520        return downloads;
521    }
522
523    private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
524        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
525
526        for (ResolutionItem item : group.items) {
527            ArtifactDownload download = item.download;
528            if (download == null) {
529                continue;
530            }
531
532            Artifact artifact = download.getArtifact();
533            if (download.getException() == null) {
534                item.resolved.set(true);
535                item.result.setRepository(group.repository);
536                try {
537                    artifact = artifact.setFile(getFile(session, artifact, download.getFile()));
538                    item.result.setArtifact(artifact);
539
540                    lrm.add(
541                            session,
542                            new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
543                } catch (ArtifactTransferException e) {
544                    download.setException(e);
545                    item.result.addException(e);
546                }
547            } else {
548                item.result.addException(download.getException());
549            }
550
551            /*
552             * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
553             * "already updated" via session data when actual update to local repo is still pending.
554             */
555            if (item.updateCheck != null) {
556                item.updateCheck.setException(download.getException());
557                updateCheckManager.touchArtifact(session, item.updateCheck);
558            }
559
560            artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
561            if (download.getException() == null) {
562                artifactResolved(session, download.getTrace(), artifact, group.repository, null);
563            }
564        }
565    }
566
567    private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
568        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
569        event.setTrace(trace);
570        event.setArtifact(artifact);
571
572        repositoryEventDispatcher.dispatch(event.build());
573    }
574
575    private void artifactResolved(
576            RepositorySystemSession session,
577            RequestTrace trace,
578            Artifact artifact,
579            ArtifactRepository repository,
580            List<Exception> exceptions) {
581        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
582        event.setTrace(trace);
583        event.setArtifact(artifact);
584        event.setRepository(repository);
585        event.setExceptions(exceptions);
586        if (artifact != null) {
587            event.setFile(artifact.getFile());
588        }
589
590        repositoryEventDispatcher.dispatch(event.build());
591    }
592
593    private void artifactDownloading(
594            RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
595        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
596        event.setTrace(trace);
597        event.setArtifact(artifact);
598        event.setRepository(repository);
599
600        repositoryEventDispatcher.dispatch(event.build());
601    }
602
603    private void artifactDownloaded(
604            RepositorySystemSession session,
605            RequestTrace trace,
606            Artifact artifact,
607            RemoteRepository repository,
608            Exception exception) {
609        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
610        event.setTrace(trace);
611        event.setArtifact(artifact);
612        event.setRepository(repository);
613        event.setException(exception);
614        if (artifact != null) {
615            event.setFile(artifact.getFile());
616        }
617
618        repositoryEventDispatcher.dispatch(event.build());
619    }
620
621    static class ResolutionGroup {
622
623        final RemoteRepository repository;
624
625        final List<ResolutionItem> items = new ArrayList<>();
626
627        ResolutionGroup(RemoteRepository repository) {
628            this.repository = repository;
629        }
630
631        boolean matches(RemoteRepository repo) {
632            return repository.getUrl().equals(repo.getUrl())
633                    && repository.getContentType().equals(repo.getContentType())
634                    && repository.isRepositoryManager() == repo.isRepositoryManager();
635        }
636    }
637
638    static class ResolutionItem {
639
640        final RequestTrace trace;
641
642        final ArtifactRequest request;
643
644        final ArtifactResult result;
645
646        final LocalArtifactResult local;
647
648        final RemoteRepository repository;
649
650        final Artifact artifact;
651
652        final AtomicBoolean resolved;
653
654        ArtifactDownload download;
655
656        UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
657
658        ResolutionItem(
659                RequestTrace trace,
660                Artifact artifact,
661                AtomicBoolean resolved,
662                ArtifactResult result,
663                LocalArtifactResult local,
664                RemoteRepository repository) {
665            this.trace = trace;
666            this.artifact = artifact;
667            this.resolved = resolved;
668            this.result = result;
669            this.request = result.getRequest();
670            this.local = local;
671            this.repository = repository;
672        }
673    }
674}