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.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.List;
030import java.util.Map;
031import java.util.Objects;
032import java.util.concurrent.atomic.AtomicBoolean;
033import java.util.concurrent.atomic.AtomicInteger;
034import java.util.function.Consumer;
035import java.util.stream.Collectors;
036
037import org.eclipse.aether.ConfigurationProperties;
038import org.eclipse.aether.RepositorySystem;
039import org.eclipse.aether.RepositorySystemSession;
040import org.eclipse.aether.RequestTrace;
041import org.eclipse.aether.SyncContext;
042import org.eclipse.aether.artifact.Artifact;
043import org.eclipse.aether.collection.CollectRequest;
044import org.eclipse.aether.collection.CollectResult;
045import org.eclipse.aether.collection.DependencyCollectionException;
046import org.eclipse.aether.deployment.DeployRequest;
047import org.eclipse.aether.deployment.DeployResult;
048import org.eclipse.aether.deployment.DeploymentException;
049import org.eclipse.aether.graph.DependencyFilter;
050import org.eclipse.aether.graph.DependencyNode;
051import org.eclipse.aether.graph.DependencyVisitor;
052import org.eclipse.aether.impl.ArtifactDescriptorReader;
053import org.eclipse.aether.impl.ArtifactResolver;
054import org.eclipse.aether.impl.DependencyCollector;
055import org.eclipse.aether.impl.Deployer;
056import org.eclipse.aether.impl.Installer;
057import org.eclipse.aether.impl.LocalRepositoryProvider;
058import org.eclipse.aether.impl.MetadataResolver;
059import org.eclipse.aether.impl.RemoteRepositoryManager;
060import org.eclipse.aether.impl.RepositorySystemLifecycle;
061import org.eclipse.aether.impl.RepositorySystemValidator;
062import org.eclipse.aether.impl.VersionRangeResolver;
063import org.eclipse.aether.impl.VersionResolver;
064import org.eclipse.aether.installation.InstallRequest;
065import org.eclipse.aether.installation.InstallResult;
066import org.eclipse.aether.installation.InstallationException;
067import org.eclipse.aether.internal.impl.session.DefaultSessionBuilder;
068import org.eclipse.aether.repository.Authentication;
069import org.eclipse.aether.repository.LocalRepository;
070import org.eclipse.aether.repository.LocalRepositoryManager;
071import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
072import org.eclipse.aether.repository.Proxy;
073import org.eclipse.aether.repository.RemoteRepository;
074import org.eclipse.aether.resolution.ArtifactDescriptorException;
075import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
076import org.eclipse.aether.resolution.ArtifactDescriptorResult;
077import org.eclipse.aether.resolution.ArtifactRequest;
078import org.eclipse.aether.resolution.ArtifactResolutionException;
079import org.eclipse.aether.resolution.ArtifactResult;
080import org.eclipse.aether.resolution.DependencyRequest;
081import org.eclipse.aether.resolution.DependencyResolutionException;
082import org.eclipse.aether.resolution.DependencyResult;
083import org.eclipse.aether.resolution.MetadataRequest;
084import org.eclipse.aether.resolution.MetadataResult;
085import org.eclipse.aether.resolution.VersionRangeRequest;
086import org.eclipse.aether.resolution.VersionRangeResolutionException;
087import org.eclipse.aether.resolution.VersionRangeResult;
088import org.eclipse.aether.resolution.VersionRequest;
089import org.eclipse.aether.resolution.VersionResolutionException;
090import org.eclipse.aether.resolution.VersionResult;
091import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator;
092import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory;
093import org.eclipse.aether.spi.synccontext.SyncContextFactory;
094import org.eclipse.aether.util.ConfigUtils;
095import org.eclipse.aether.util.graph.visitor.LevelOrderDependencyNodeConsumerVisitor;
096import org.eclipse.aether.util.graph.visitor.PostorderDependencyNodeConsumerVisitor;
097import org.eclipse.aether.util.graph.visitor.PreorderDependencyNodeConsumerVisitor;
098import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
099
100import static java.util.Objects.requireNonNull;
101import static java.util.stream.Collectors.toList;
102
103/**
104 *
105 */
106@Singleton
107@Named
108public class DefaultRepositorySystem implements RepositorySystem {
109    /**
110     * Sentinel object placed into the {@link RequestTrace} chain by each public method
111     * to detect re-entrant calls into this {@code RepositorySystem}.
112     * <p>
113     * The resolver's architecture assumes that internal components ({@code ArtifactResolver},
114     * {@code DependencyCollector}, {@code ArtifactDescriptorReader}, etc.) call each other
115     * directly via the internal API in {@code org.eclipse.aether.impl}, never going through
116     * the public {@code RepositorySystem} facade. This means session validation, request
117     * validation, and artifact decoration only need to run once — on the outermost call.
118     * <p>
119     * However, some {@code RepositorySystem} consumers (notably Maven 4's
120     * {@code ArtifactDescriptorReader} → {@code ModelBuilder} → {@code ModelResolver} chain)
121     * re-enter {@code RepositorySystem} during an ongoing operation. Without this guard,
122     * every re-entrant call redundantly validates the session, validates the request (which
123     * may reject intermediate state like uninterpolated expressions from transitive POMs),
124     * and applies artifact decorators. This causes both correctness issues (false validation
125     * failures) and unnecessary performance overhead.
126     * <p>
127     * Re-entrancy can happen on the <em>same</em> thread (e.g. {@code collectDependencies}
128     * → {@code readArtifactDescriptor} → model builder → model resolver → {@code resolveVersionRange})
129     * or on a <em>different</em> thread when the resolver uses internal parallelism (e.g.
130     * {@code BfDependencyCollector}'s {@code SmartExecutor} dispatches descriptor resolution
131     * to pool threads, which then re-enter {@code RepositorySystem} via the model resolver).
132     * <p>
133     * To handle both cases, we leverage the {@link RequestTrace} chain that is already
134     * propagated across threads by the caller (e.g. Maven's model builder explicitly copies
135     * traces to pool threads). On the outermost call, we stamp this marker into the request's
136     * trace. On re-entry — whether on the same thread or a pool thread — the marker is found
137     * in the trace ancestry, so validation and decoration are skipped.
138     */
139    private static final Object REPOSITORY_SYSTEM_CALL = new Object() {
140        @Override
141        public String toString() {
142            return "RepositorySystem";
143        }
144    };
145
146    private final AtomicBoolean shutdown;
147
148    private final AtomicInteger sessionIdCounter;
149
150    private final VersionResolver versionResolver;
151
152    private final VersionRangeResolver versionRangeResolver;
153
154    private final ArtifactResolver artifactResolver;
155
156    private final MetadataResolver metadataResolver;
157
158    private final ArtifactDescriptorReader artifactDescriptorReader;
159
160    private final DependencyCollector dependencyCollector;
161
162    private final Installer installer;
163
164    private final Deployer deployer;
165
166    private final LocalRepositoryProvider localRepositoryProvider;
167
168    private final SyncContextFactory syncContextFactory;
169
170    private final RemoteRepositoryManager remoteRepositoryManager;
171
172    private final RepositorySystemLifecycle repositorySystemLifecycle;
173
174    private final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories;
175
176    private final RepositorySystemValidator repositorySystemValidator;
177
178    @SuppressWarnings("checkstyle:parameternumber")
179    @Inject
180    public DefaultRepositorySystem(
181            VersionResolver versionResolver,
182            VersionRangeResolver versionRangeResolver,
183            ArtifactResolver artifactResolver,
184            MetadataResolver metadataResolver,
185            ArtifactDescriptorReader artifactDescriptorReader,
186            DependencyCollector dependencyCollector,
187            Installer installer,
188            Deployer deployer,
189            LocalRepositoryProvider localRepositoryProvider,
190            SyncContextFactory syncContextFactory,
191            RemoteRepositoryManager remoteRepositoryManager,
192            RepositorySystemLifecycle repositorySystemLifecycle,
193            Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories,
194            RepositorySystemValidator repositorySystemValidator) {
195        this.shutdown = new AtomicBoolean(false);
196        this.sessionIdCounter = new AtomicInteger(0);
197        this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
198        this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null");
199        this.artifactResolver = requireNonNull(artifactResolver, "artifact resolver cannot be null");
200        this.metadataResolver = requireNonNull(metadataResolver, "metadata resolver cannot be null");
201        this.artifactDescriptorReader =
202                requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null");
203        this.dependencyCollector = requireNonNull(dependencyCollector, "dependency collector cannot be null");
204        this.installer = requireNonNull(installer, "installer cannot be null");
205        this.deployer = requireNonNull(deployer, "deployer cannot be null");
206        this.localRepositoryProvider =
207                requireNonNull(localRepositoryProvider, "local repository provider cannot be null");
208        this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
209        this.remoteRepositoryManager =
210                requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
211        this.repositorySystemLifecycle =
212                requireNonNull(repositorySystemLifecycle, "repository system lifecycle cannot be null");
213        this.artifactDecoratorFactories =
214                requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null");
215        this.repositorySystemValidator =
216                requireNonNull(repositorySystemValidator, "repository system validator cannot be null");
217    }
218
219    @Override
220    public VersionResult resolveVersion(RepositorySystemSession session, VersionRequest request)
221            throws VersionResolutionException {
222        requireNonNull(request, "request cannot be null");
223        if (!isReentrant(request.getTrace())) {
224            validateSession(session);
225            repositorySystemValidator.validateVersionRequest(session, request);
226            request.setTrace(stampReentrancyMarker(request.getTrace()));
227        }
228        return versionResolver.resolveVersion(session, request);
229    }
230
231    @Override
232    public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
233            throws VersionRangeResolutionException {
234        requireNonNull(request, "request cannot be null");
235        if (!isReentrant(request.getTrace())) {
236            validateSession(session);
237            repositorySystemValidator.validateVersionRangeRequest(session, request);
238            request.setTrace(stampReentrancyMarker(request.getTrace()));
239        }
240        return versionRangeResolver.resolveVersionRange(session, request);
241    }
242
243    @Override
244    public ArtifactDescriptorResult readArtifactDescriptor(
245            RepositorySystemSession session, ArtifactDescriptorRequest request) throws ArtifactDescriptorException {
246        requireNonNull(request, "request cannot be null");
247        boolean outermost = !isReentrant(request.getTrace());
248        if (outermost) {
249            validateSession(session);
250            repositorySystemValidator.validateArtifactDescriptorRequest(session, request);
251            request.setTrace(stampReentrancyMarker(request.getTrace()));
252        }
253        ArtifactDescriptorResult descriptorResult = artifactDescriptorReader.readArtifactDescriptor(session, request);
254        if (outermost) {
255            for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
256                descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
257            }
258        }
259        return descriptorResult;
260    }
261
262    @Override
263    public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
264            throws ArtifactResolutionException {
265        requireNonNull(request, "request cannot be null");
266        if (!isReentrant(request.getTrace())) {
267            validateSession(session);
268            repositorySystemValidator.validateArtifactRequests(session, Collections.singleton(request));
269            request.setTrace(stampReentrancyMarker(request.getTrace()));
270        }
271        return artifactResolver.resolveArtifact(session, request);
272    }
273
274    @Override
275    public List<ArtifactResult> resolveArtifacts(
276            RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
277            throws ArtifactResolutionException {
278        requireNonNull(requests, "requests cannot be null");
279        // All requests in a batch share the same trace context, so checking any one is sufficient.
280        RequestTrace firstTrace = requests.stream()
281                .map(ArtifactRequest::getTrace)
282                .filter(Objects::nonNull)
283                .findFirst()
284                .orElse(null);
285        if (!isReentrant(firstTrace)) {
286            validateSession(session);
287            repositorySystemValidator.validateArtifactRequests(session, requests);
288            for (ArtifactRequest request : requests) {
289                request.setTrace(stampReentrancyMarker(request.getTrace()));
290            }
291        }
292        return artifactResolver.resolveArtifacts(session, requests);
293    }
294
295    @Override
296    public List<MetadataResult> resolveMetadata(
297            RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
298        requireNonNull(requests, "requests cannot be null");
299        // All requests in a batch share the same trace context, so checking any one is sufficient.
300        RequestTrace firstTrace = requests.stream()
301                .map(MetadataRequest::getTrace)
302                .filter(Objects::nonNull)
303                .findFirst()
304                .orElse(null);
305        if (!isReentrant(firstTrace)) {
306            validateSession(session);
307            repositorySystemValidator.validateMetadataRequests(session, requests);
308            for (MetadataRequest request : requests) {
309                request.setTrace(stampReentrancyMarker(request.getTrace()));
310            }
311        }
312        return metadataResolver.resolveMetadata(session, requests);
313    }
314
315    @Override
316    public CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request)
317            throws DependencyCollectionException {
318        requireNonNull(request, "request cannot be null");
319        if (!isReentrant(request.getTrace())) {
320            validateSession(session);
321            repositorySystemValidator.validateCollectRequest(session, request);
322            request.setTrace(stampReentrancyMarker(request.getTrace()));
323        }
324        return dependencyCollector.collectDependencies(session, request);
325    }
326
327    @Override
328    public DependencyResult resolveDependencies(RepositorySystemSession session, DependencyRequest request)
329            throws DependencyResolutionException {
330        requireNonNull(request, "request cannot be null");
331        if (!isReentrant(request.getTrace())) {
332            validateSession(session);
333            repositorySystemValidator.validateDependencyRequest(session, request);
334            request.setTrace(stampReentrancyMarker(request.getTrace()));
335        }
336        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
337
338        DependencyResult result = new DependencyResult(request);
339
340        DependencyCollectionException dce = null;
341        ArtifactResolutionException are = null;
342
343        if (request.getRoot() != null) {
344            result.setRoot(request.getRoot());
345        } else if (request.getCollectRequest() != null) {
346            CollectResult collectResult;
347            try {
348                request.getCollectRequest().setTrace(trace);
349                collectResult = dependencyCollector.collectDependencies(session, request.getCollectRequest());
350            } catch (DependencyCollectionException e) {
351                dce = e;
352                collectResult = e.getResult();
353            }
354            result.setRoot(collectResult.getRoot());
355            result.setCycles(collectResult.getCycles());
356            result.setCollectExceptions(collectResult.getExceptions());
357        } else {
358            throw new NullPointerException("dependency node and collect request cannot be null");
359        }
360
361        final List<DependencyNode> dependencyNodes =
362                doFlattenDependencyNodes(session, result.getRoot(), request.getFilter());
363
364        final List<ArtifactRequest> requests = dependencyNodes.stream()
365                .map(n -> {
366                    if (n.getDependency() != null) {
367                        ArtifactRequest artifactRequest = new ArtifactRequest(n);
368                        artifactRequest.setTrace(trace);
369                        return artifactRequest;
370                    } else {
371                        return null;
372                    }
373                })
374                .filter(Objects::nonNull)
375                .collect(Collectors.toList());
376        List<ArtifactResult> results;
377        try {
378            results = artifactResolver.resolveArtifacts(session, requests);
379        } catch (ArtifactResolutionException e) {
380            are = e;
381            results = e.getResults();
382        }
383        result.setDependencyNodeResults(dependencyNodes);
384        result.setArtifactResults(results);
385
386        updateNodesWithResolvedArtifacts(results);
387
388        if (dce != null) {
389            throw new DependencyResolutionException(result, dce);
390        } else if (are != null) {
391            throw new DependencyResolutionException(result, are);
392        }
393
394        return result;
395    }
396
397    @Override
398    public List<DependencyNode> flattenDependencyNodes(
399            RepositorySystemSession session, DependencyNode root, DependencyFilter dependencyFilter) {
400        validateSession(session);
401        requireNonNull(root, "root cannot be null");
402        return doFlattenDependencyNodes(session, root, dependencyFilter);
403    }
404
405    private List<DependencyNode> doFlattenDependencyNodes(
406            RepositorySystemSession session, DependencyNode root, DependencyFilter dependencyFilter) {
407        final ArrayList<DependencyNode> dependencyNodes = new ArrayList<>();
408        if (root != null) {
409            root.accept(getDependencyVisitor(session, dependencyNodes::add, dependencyFilter));
410        }
411        return dependencyNodes;
412    }
413
414    private DependencyVisitor getDependencyVisitor(
415            RepositorySystemSession session, Consumer<DependencyNode> nodeConsumer, DependencyFilter dependencyFilter) {
416        String strategy = ConfigUtils.getString(
417                session,
418                ConfigurationProperties.DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR,
419                ConfigurationProperties.REPOSITORY_SYSTEM_DEPENDENCY_VISITOR);
420        switch (strategy) {
421            case PreorderDependencyNodeConsumerVisitor.NAME:
422                return new PreorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
423            case PostorderDependencyNodeConsumerVisitor.NAME:
424                return new PostorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
425            case LevelOrderDependencyNodeConsumerVisitor.NAME:
426                return new LevelOrderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
427            default:
428                throw new IllegalArgumentException("Invalid dependency visitor strategy: " + strategy);
429        }
430    }
431
432    private void updateNodesWithResolvedArtifacts(List<ArtifactResult> results) {
433        for (ArtifactResult result : results) {
434            Artifact artifact = result.getArtifact();
435            if (artifact != null) {
436                result.getRequest().getDependencyNode().setArtifact(artifact);
437            }
438        }
439    }
440
441    @Override
442    public InstallResult install(RepositorySystemSession session, InstallRequest request) throws InstallationException {
443        validateSession(session);
444        requireNonNull(request, "request cannot be null");
445        repositorySystemValidator.validateInstallRequest(session, request);
446        return installer.install(session, request);
447    }
448
449    @Override
450    public DeployResult deploy(RepositorySystemSession session, DeployRequest request) throws DeploymentException {
451        validateSession(session);
452        requireNonNull(request, "request cannot be null");
453        repositorySystemValidator.validateDeployRequest(session, request);
454        return deployer.deploy(session, request);
455    }
456
457    @Override
458    public LocalRepositoryManager newLocalRepositoryManager(
459            RepositorySystemSession session, LocalRepository localRepository) {
460        requireNonNull(session, "session cannot be null");
461        requireNonNull(localRepository, "localRepository cannot be null");
462        validateSystem();
463        repositorySystemValidator.validateLocalRepositories(session, Collections.singleton(localRepository));
464        return createLocalRepositoryManager(session, localRepository);
465    }
466
467    @Override
468    public LocalRepositoryManager newLocalRepositoryManager(
469            RepositorySystemSession session, LocalRepository... localRepositories) {
470        requireNonNull(session, "session cannot be null");
471        requireNonNull(localRepositories, "localRepositories cannot be null");
472        validateSystem();
473        repositorySystemValidator.validateLocalRepositories(session, Arrays.asList(localRepositories));
474        return createLocalRepositoryManager(session, Arrays.asList(localRepositories));
475    }
476
477    @Override
478    public LocalRepositoryManager newLocalRepositoryManager(
479            RepositorySystemSession session, List<LocalRepository> localRepositories) {
480        requireNonNull(session, "session cannot be null");
481        requireNonNull(localRepositories, "localRepositories cannot be null");
482        validateSystem();
483        repositorySystemValidator.validateLocalRepositories(session, localRepositories);
484        return createLocalRepositoryManager(session, localRepositories);
485    }
486
487    private LocalRepositoryManager createLocalRepositoryManager(
488            RepositorySystemSession session, List<LocalRepository> localRepositories) {
489        if (localRepositories.isEmpty()) {
490            throw new IllegalArgumentException("empty localRepositories");
491        } else if (localRepositories.size() == 1) {
492            return createLocalRepositoryManager(session, localRepositories.get(0));
493        } else {
494            LocalRepositoryManager head = createLocalRepositoryManager(session, localRepositories.get(0));
495            List<LocalRepositoryManager> tail = localRepositories.subList(1, localRepositories.size()).stream()
496                    .map(l -> createLocalRepositoryManager(session, l))
497                    .collect(toList());
498            return new ChainedLocalRepositoryManager(head, tail, session);
499        }
500    }
501
502    private LocalRepositoryManager createLocalRepositoryManager(
503            RepositorySystemSession session, LocalRepository localRepository) {
504        try {
505            return localRepositoryProvider.newLocalRepositoryManager(session, localRepository);
506        } catch (NoLocalRepositoryManagerException e) {
507            throw new IllegalArgumentException(e.getMessage(), e);
508        }
509    }
510
511    @Override
512    public SyncContext newSyncContext(RepositorySystemSession session, boolean shared) {
513        validateSession(session);
514        return syncContextFactory.newInstance(session, shared);
515    }
516
517    @Override
518    public List<RemoteRepository> newResolutionRepositories(
519            RepositorySystemSession session, List<RemoteRepository> repositories) {
520        validateSession(session);
521        validateRepositories(repositories);
522        repositorySystemValidator.validateRemoteRepositories(session, repositories);
523        return remoteRepositoryManager.aggregateRepositories(session, new ArrayList<>(), repositories, true);
524    }
525
526    @Override
527    public RemoteRepository newDeploymentRepository(RepositorySystemSession session, RemoteRepository repository) {
528        validateSession(session);
529        requireNonNull(repository, "repository cannot be null");
530        repositorySystemValidator.validateRemoteRepositories(session, Collections.singletonList(repository));
531        Authentication auth = session.getAuthenticationSelector().getAuthentication(repository);
532        Proxy proxy = session.getProxySelector().getProxy(repository);
533        return new RemoteRepository.Builder(repository)
534                .setAuthentication(auth)
535                .setProxy(proxy)
536                .setIntent(RemoteRepository.Intent.DEPLOYMENT)
537                .build();
538    }
539
540    @Override
541    public void addOnSystemEndedHandler(Runnable handler) {
542        validateSystem();
543        repositorySystemLifecycle.addOnSystemEndedHandler(handler);
544    }
545
546    @Override
547    public RepositorySystemSession.SessionBuilder createSessionBuilder() {
548        validateSystem();
549        return new DefaultSessionBuilder(
550                this, repositorySystemLifecycle, () -> "id-" + sessionIdCounter.incrementAndGet());
551    }
552
553    @Override
554    public void shutdown() {
555        if (shutdown.compareAndSet(false, true)) {
556            repositorySystemLifecycle.systemEnded();
557        }
558    }
559
560    /**
561     * Stamps the {@link #REPOSITORY_SYSTEM_CALL} re-entrancy marker into the trace chain
562     * while preserving the original trace tip data. The marker is inserted <em>below</em>
563     * the tip so that code walking the trace and casting {@code getData()} to its expected
564     * type (e.g. {@code org.apache.maven.artifact.Artifact}) still finds the original data
565     * at the tip rather than the anonymous marker object.
566     *
567     * @param currentTrace the current request trace (may be {@code null})
568     * @return a new trace with the marker inserted and original tip data preserved
569     */
570    private static RequestTrace stampReentrancyMarker(RequestTrace currentTrace) {
571        RequestTrace markerTrace = RequestTrace.newChild(currentTrace, REPOSITORY_SYSTEM_CALL);
572        return currentTrace != null ? RequestTrace.newChild(markerTrace, currentTrace.getData()) : markerTrace;
573    }
574
575    /**
576     * Checks whether the given {@link RequestTrace} indicates a re-entrant call by looking
577     * for the {@link #REPOSITORY_SYSTEM_CALL} marker in the trace ancestry.
578     *
579     * @return {@code true} if the marker is found (re-entrant call), {@code false} otherwise
580     */
581    private static boolean isReentrant(RequestTrace trace) {
582        for (RequestTrace t = trace; t != null; t = t.getParent()) {
583            if (t.getData() == REPOSITORY_SYSTEM_CALL) {
584                return true;
585            }
586        }
587        return false;
588    }
589
590    private void validateSession(RepositorySystemSession session) {
591        requireNonNull(session, "repository system session cannot be null");
592        invalidSession(session.getLocalRepositoryManager(), "local repository manager");
593        invalidSession(session.getSystemProperties(), "system properties");
594        invalidSession(session.getUserProperties(), "user properties");
595        invalidSession(session.getConfigProperties(), "config properties");
596        invalidSession(session.getMirrorSelector(), "mirror selector");
597        invalidSession(session.getProxySelector(), "proxy selector");
598        invalidSession(session.getAuthenticationSelector(), "authentication selector");
599        invalidSession(session.getArtifactTypeRegistry(), "artifact type registry");
600        invalidSession(session.getData(), "data");
601        validateSystem();
602    }
603
604    private void validateSystem() {
605        if (shutdown.get()) {
606            throw new IllegalStateException("repository system is already shut down");
607        }
608    }
609
610    private void validateRepositories(List<RemoteRepository> repositories) {
611        requireNonNull(repositories, "repositories cannot be null");
612        for (RemoteRepository repository : repositories) {
613            requireNonNull(repository, "repository cannot be null");
614        }
615    }
616
617    private void invalidSession(Object obj, String name) {
618        requireNonNull(obj, "repository system session's " + name + " cannot be null");
619    }
620}