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.internal.impl.resolver;
20  
21  import java.util.HashMap;
22  import java.util.LinkedHashSet;
23  import java.util.Map;
24  import java.util.Objects;
25  
26  import org.apache.maven.api.Session;
27  import org.apache.maven.api.di.Inject;
28  import org.apache.maven.api.di.Named;
29  import org.apache.maven.api.di.Singleton;
30  import org.apache.maven.api.model.Model;
31  import org.apache.maven.api.services.ModelBuilder;
32  import org.apache.maven.api.services.ModelBuilderException;
33  import org.apache.maven.api.services.ModelBuilderRequest;
34  import org.apache.maven.api.services.ModelProblem;
35  import org.apache.maven.api.services.ModelRepositoryHolder;
36  import org.apache.maven.api.services.ModelResolver;
37  import org.apache.maven.api.services.ModelResolverException;
38  import org.apache.maven.api.services.ModelSource;
39  import org.apache.maven.internal.impl.InternalSession;
40  import org.eclipse.aether.RepositoryEvent;
41  import org.eclipse.aether.RepositoryEvent.EventType;
42  import org.eclipse.aether.RepositoryException;
43  import org.eclipse.aether.RepositorySystemSession;
44  import org.eclipse.aether.RequestTrace;
45  import org.eclipse.aether.artifact.Artifact;
46  import org.eclipse.aether.impl.ArtifactDescriptorReader;
47  import org.eclipse.aether.impl.ArtifactResolver;
48  import org.eclipse.aether.impl.RemoteRepositoryManager;
49  import org.eclipse.aether.impl.RepositoryEventDispatcher;
50  import org.eclipse.aether.impl.VersionRangeResolver;
51  import org.eclipse.aether.impl.VersionResolver;
52  import org.eclipse.aether.repository.WorkspaceReader;
53  import org.eclipse.aether.resolution.ArtifactDescriptorException;
54  import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
55  import org.eclipse.aether.resolution.ArtifactDescriptorPolicyRequest;
56  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
57  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
58  import org.eclipse.aether.resolution.ArtifactRequest;
59  import org.eclipse.aether.resolution.ArtifactResolutionException;
60  import org.eclipse.aether.resolution.ArtifactResult;
61  import org.eclipse.aether.resolution.VersionRequest;
62  import org.eclipse.aether.resolution.VersionResolutionException;
63  import org.eclipse.aether.resolution.VersionResult;
64  import org.eclipse.aether.transfer.ArtifactNotFoundException;
65  
66  /**
67   * Default artifact descriptor reader.
68   */
69  @Named
70  @Singleton
71  public class DefaultArtifactDescriptorReader implements ArtifactDescriptorReader {
72      private final RemoteRepositoryManager remoteRepositoryManager;
73      private final VersionResolver versionResolver;
74      private final VersionRangeResolver versionRangeResolver;
75      private final ArtifactResolver artifactResolver;
76      private final RepositoryEventDispatcher repositoryEventDispatcher;
77      private final ModelBuilder modelBuilder;
78      private final Map<String, MavenArtifactRelocationSource> artifactRelocationSources;
79      private final ArtifactDescriptorReaderDelegate delegate;
80  
81      @Inject
82      public DefaultArtifactDescriptorReader(
83              RemoteRepositoryManager remoteRepositoryManager,
84              VersionResolver versionResolver,
85              VersionRangeResolver versionRangeResolver,
86              ArtifactResolver artifactResolver,
87              ModelBuilder modelBuilder,
88              RepositoryEventDispatcher repositoryEventDispatcher,
89              Map<String, MavenArtifactRelocationSource> artifactRelocationSources) {
90          this.remoteRepositoryManager =
91                  Objects.requireNonNull(remoteRepositoryManager, "remoteRepositoryManager cannot be null");
92          this.versionResolver = Objects.requireNonNull(versionResolver, "versionResolver cannot be null");
93          this.versionRangeResolver = Objects.requireNonNull(versionRangeResolver, "versionRangeResolver cannot be null");
94          this.artifactResolver = Objects.requireNonNull(artifactResolver, "artifactResolver cannot be null");
95          this.modelBuilder = Objects.requireNonNull(modelBuilder, "modelBuilder cannot be null");
96          this.repositoryEventDispatcher =
97                  Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
98          this.artifactRelocationSources =
99                  Objects.requireNonNull(artifactRelocationSources, "artifactRelocationSources cannot be null");
100         this.delegate = new ArtifactDescriptorReaderDelegate();
101     }
102 
103     @Override
104     public ArtifactDescriptorResult readArtifactDescriptor(
105             RepositorySystemSession session, ArtifactDescriptorRequest request) throws ArtifactDescriptorException {
106         ArtifactDescriptorResult result = new ArtifactDescriptorResult(request);
107 
108         Model model = loadPom(session, request, result);
109         if (model != null) {
110             Map<String, Object> config = session.getConfigProperties();
111             ArtifactDescriptorReaderDelegate delegate =
112                     (ArtifactDescriptorReaderDelegate) config.get(ArtifactDescriptorReaderDelegate.class.getName());
113 
114             if (delegate == null) {
115                 delegate = this.delegate;
116             }
117 
118             delegate.populateResult(InternalSession.from(session), result, model);
119         }
120 
121         return result;
122     }
123 
124     private Model loadPom(
125             RepositorySystemSession session, ArtifactDescriptorRequest request, ArtifactDescriptorResult result)
126             throws ArtifactDescriptorException {
127         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
128 
129         LinkedHashSet<String> visited = new LinkedHashSet<>();
130         for (Artifact a = request.getArtifact(); ; ) {
131             Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifactUnconditionally(a);
132             try {
133                 VersionRequest versionRequest =
134                         new VersionRequest(a, request.getRepositories(), request.getRequestContext());
135                 versionRequest.setTrace(trace);
136                 VersionResult versionResult = versionResolver.resolveVersion(session, versionRequest);
137 
138                 a = a.setVersion(versionResult.getVersion());
139 
140                 versionRequest =
141                         new VersionRequest(pomArtifact, request.getRepositories(), request.getRequestContext());
142                 versionRequest.setTrace(trace);
143                 versionResult = versionResolver.resolveVersion(session, versionRequest);
144 
145                 pomArtifact = pomArtifact.setVersion(versionResult.getVersion());
146             } catch (VersionResolutionException e) {
147                 result.addException(e);
148                 throw new ArtifactDescriptorException(result);
149             }
150 
151             if (!visited.add(a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getBaseVersion())) {
152                 RepositoryException exception =
153                         new RepositoryException("Artifact relocations form a cycle: " + visited);
154                 invalidDescriptor(session, trace, a, exception);
155                 if ((getPolicy(session, a, request) & ArtifactDescriptorPolicy.IGNORE_INVALID) != 0) {
156                     return null;
157                 }
158                 result.addException(exception);
159                 throw new ArtifactDescriptorException(result);
160             }
161 
162             ArtifactResult resolveResult;
163             try {
164                 ArtifactRequest resolveRequest =
165                         new ArtifactRequest(pomArtifact, request.getRepositories(), request.getRequestContext());
166                 resolveRequest.setTrace(trace);
167                 resolveResult = artifactResolver.resolveArtifact(session, resolveRequest);
168                 pomArtifact = resolveResult.getArtifact();
169                 result.setRepository(resolveResult.getRepository());
170             } catch (ArtifactResolutionException e) {
171                 if (e.getCause() instanceof ArtifactNotFoundException) {
172                     missingDescriptor(session, trace, a, (Exception) e.getCause());
173                     if ((getPolicy(session, a, request) & ArtifactDescriptorPolicy.IGNORE_MISSING) != 0) {
174                         return null;
175                     }
176                 }
177                 result.addException(e);
178                 throw new ArtifactDescriptorException(result);
179             }
180 
181             Model model;
182 
183             // TODO hack: don't rebuild model if it was already loaded during reactor resolution
184             final WorkspaceReader workspace = session.getWorkspaceReader();
185             if (workspace instanceof MavenWorkspaceReader) {
186                 model = ((MavenWorkspaceReader) workspace).findModel(pomArtifact);
187                 if (model != null) {
188                     return model;
189                 }
190             }
191 
192             try {
193                 InternalSession iSession = InternalSession.from(session);
194                 Session iSessionWithRepos = iSession.withRemoteRepositories(request.getRepositories().stream()
195                         .map(iSession::getRemoteRepository)
196                         .toList());
197                 String gav =
198                         pomArtifact.getGroupId() + ":" + pomArtifact.getArtifactId() + ":" + pomArtifact.getVersion();
199                 ModelResolver modelResolver = new DefaultModelResolver();
200                 ModelRepositoryHolder modelRepositoryHolder = new DefaultModelRepositoryHolder(
201                         iSessionWithRepos,
202                         DefaultModelRepositoryHolder.RepositoryMerging.REQUEST_DOMINANT,
203                         iSessionWithRepos.getRemoteRepositories());
204                 ModelBuilderRequest modelRequest = ModelBuilderRequest.builder()
205                         .session(iSessionWithRepos)
206                         .projectBuild(false)
207                         .processPlugins(false)
208                         .twoPhaseBuilding(false)
209                         .source(ModelSource.fromPath(pomArtifact.getPath(), gav))
210                         // This merge is on purpose because otherwise user properties would override model
211                         // properties in dependencies the user does not know. See MNG-7563 for details.
212                         .systemProperties(toProperties(session.getUserProperties(), session.getSystemProperties()))
213                         .userProperties(Map.of())
214                         .modelResolver(modelResolver)
215                         .modelRepositoryHolder(modelRepositoryHolder)
216                         .modelCache(DefaultModelCache.newInstance(session, false))
217                         .build();
218 
219                 model = modelBuilder.build(modelRequest).getEffectiveModel();
220             } catch (ModelBuilderException e) {
221                 for (ModelProblem problem : e.getResult().getProblems()) {
222                     if (problem.getException() instanceof ModelResolverException) {
223                         result.addException(problem.getException());
224                         throw new ArtifactDescriptorException(result);
225                     }
226                 }
227                 invalidDescriptor(session, trace, a, e);
228                 if ((getPolicy(session, a, request) & ArtifactDescriptorPolicy.IGNORE_INVALID) != 0) {
229                     return null;
230                 }
231                 result.addException(e);
232                 throw new ArtifactDescriptorException(result);
233             }
234 
235             Artifact relocatedArtifact = getRelocation(session, result, model);
236             if (relocatedArtifact != null) {
237                 if (withinSameGav(relocatedArtifact, a)) {
238                     result.setArtifact(relocatedArtifact);
239                     return model; // they share same model
240                 } else {
241                     result.addRelocation(a);
242                     a = relocatedArtifact;
243                     result.setArtifact(a);
244                 }
245             } else {
246                 return model;
247             }
248         }
249     }
250 
251     private boolean withinSameGav(Artifact a1, Artifact a2) {
252         return Objects.equals(a1.getGroupId(), a2.getGroupId())
253                 && Objects.equals(a1.getArtifactId(), a2.getArtifactId())
254                 && Objects.equals(a1.getVersion(), a2.getVersion());
255     }
256 
257     private Map<String, String> toProperties(Map<String, String> dominant, Map<String, String> recessive) {
258         Map<String, String> props = new HashMap<>();
259         if (recessive != null) {
260             props.putAll(recessive);
261         }
262         if (dominant != null) {
263             props.putAll(dominant);
264         }
265         return props;
266     }
267 
268     private Artifact getRelocation(
269             RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model)
270             throws ArtifactDescriptorException {
271         Artifact result = null;
272         for (MavenArtifactRelocationSource source : artifactRelocationSources.values()) {
273             result = source.relocatedTarget(session, artifactDescriptorResult, model);
274             if (result != null) {
275                 break;
276             }
277         }
278         return result;
279     }
280 
281     private void missingDescriptor(
282             RepositorySystemSession session, RequestTrace trace, Artifact artifact, Exception exception) {
283         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DESCRIPTOR_MISSING);
284         event.setTrace(trace);
285         event.setArtifact(artifact);
286         event.setException(exception);
287 
288         repositoryEventDispatcher.dispatch(event.build());
289     }
290 
291     private void invalidDescriptor(
292             RepositorySystemSession session, RequestTrace trace, Artifact artifact, Exception exception) {
293         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DESCRIPTOR_INVALID);
294         event.setTrace(trace);
295         event.setArtifact(artifact);
296         event.setException(exception);
297 
298         repositoryEventDispatcher.dispatch(event.build());
299     }
300 
301     private int getPolicy(RepositorySystemSession session, Artifact a, ArtifactDescriptorRequest request) {
302         ArtifactDescriptorPolicy policy = session.getArtifactDescriptorPolicy();
303         if (policy == null) {
304             return ArtifactDescriptorPolicy.STRICT;
305         }
306         return policy.getPolicy(session, new ArtifactDescriptorPolicyRequest(a, request.getRequestContext()));
307     }
308 }