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.aether;
20  
21  import static java.util.stream.Collectors.toList;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.LinkedHashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.stream.Collectors;
36  import javax.inject.Inject;
37  import javax.inject.Named;
38  import org.apache.maven.RepositoryUtils;
39  import org.apache.maven.api.xml.Dom;
40  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
41  import org.apache.maven.bridge.MavenRepositorySystem;
42  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
43  import org.apache.maven.execution.MavenExecutionRequest;
44  import org.apache.maven.feature.Features;
45  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
46  import org.apache.maven.internal.xml.Xpp3Dom;
47  import org.apache.maven.model.ModelBase;
48  import org.apache.maven.model.building.TransformerContext;
49  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
50  import org.apache.maven.rtinfo.RuntimeInformation;
51  import org.apache.maven.settings.Mirror;
52  import org.apache.maven.settings.Proxy;
53  import org.apache.maven.settings.Server;
54  import org.apache.maven.settings.building.SettingsProblem;
55  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
56  import org.apache.maven.settings.crypto.SettingsDecrypter;
57  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
58  import org.codehaus.plexus.configuration.PlexusConfiguration;
59  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
60  import org.eclipse.aether.ConfigurationProperties;
61  import org.eclipse.aether.DefaultRepositorySystemSession;
62  import org.eclipse.aether.RepositorySystem;
63  import org.eclipse.aether.SessionData;
64  import org.eclipse.aether.artifact.Artifact;
65  import org.eclipse.aether.repository.LocalRepository;
66  import org.eclipse.aether.repository.LocalRepositoryManager;
67  import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
68  import org.eclipse.aether.repository.RepositoryPolicy;
69  import org.eclipse.aether.repository.WorkspaceReader;
70  import org.eclipse.aether.resolution.ResolutionErrorPolicy;
71  import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
72  import org.eclipse.aether.transform.FileTransformer;
73  import org.eclipse.aether.transform.TransformException;
74  import org.eclipse.aether.util.ConfigUtils;
75  import org.eclipse.aether.util.listener.ChainedRepositoryListener;
76  import org.eclipse.aether.util.repository.AuthenticationBuilder;
77  import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
78  import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
79  import org.eclipse.aether.util.repository.DefaultMirrorSelector;
80  import org.eclipse.aether.util.repository.DefaultProxySelector;
81  import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy;
82  import org.eclipse.sisu.Nullable;
83  import org.slf4j.Logger;
84  import org.slf4j.LoggerFactory;
85  
86  /**
87   * @since 3.3.0
88   */
89  @Named
90  public class DefaultRepositorySystemSessionFactory {
91      /**
92       * User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with
93       * {@link ChainedLocalRepositoryManager}.
94       * Default value: {@code null}, no chained LRM is used.
95       *
96       * @since 3.9.0
97       */
98      private static final String MAVEN_REPO_LOCAL_TAIL = "maven.repo.local.tail";
99  
100     /**
101      * User property for chained LRM: should artifact availability be ignored in tail local repositories or not.
102      * Default: {@code true}, will ignore availability from tail local repositories.
103      *
104      * @since 3.9.0
105      */
106     private static final String MAVEN_REPO_LOCAL_TAIL_IGNORE_AVAILABILITY = "maven.repo.local.tail.ignoreAvailability";
107 
108     /**
109      * User property for reverse dependency tree. If enabled, Maven will record ".tracking" directory into local
110      * repository with "reverse dependency tree", essentially explaining WHY given artifact is present in local
111      * repository.
112      * Default: {@code false}, will not record anything.
113      *
114      * @since 3.9.0
115      */
116     private static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree";
117 
118     private static final String MAVEN_RESOLVER_TRANSPORT_KEY = "maven.resolver.transport";
119 
120     private static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
121 
122     private static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
123 
124     private static final String MAVEN_RESOLVER_TRANSPORT_NATIVE = "native";
125 
126     private static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
127 
128     private static final String WAGON_TRANSPORTER_PRIORITY_KEY = "aether.priority.WagonTransporterFactory";
129 
130     private static final String NATIVE_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.HttpTransporterFactory";
131 
132     private static final String NATIVE_FILE_TRANSPORTER_PRIORITY_KEY = "aether.priority.FileTransporterFactory";
133 
134     private static final String RESOLVER_MAX_PRIORITY = String.valueOf(Float.MAX_VALUE);
135 
136     private final Logger logger = LoggerFactory.getLogger(getClass());
137 
138     private final ArtifactHandlerManager artifactHandlerManager;
139 
140     private final RepositorySystem repoSystem;
141 
142     private final LocalRepositoryManagerFactory simpleLocalRepoMgrFactory;
143 
144     private final WorkspaceReader workspaceRepository;
145 
146     private final SettingsDecrypter settingsDecrypter;
147 
148     private final EventSpyDispatcher eventSpyDispatcher;
149 
150     private final MavenRepositorySystem mavenRepositorySystem;
151 
152     private final RuntimeInformation runtimeInformation;
153 
154     @SuppressWarnings("checkstyle:ParameterNumber")
155     @Inject
156     public DefaultRepositorySystemSessionFactory(
157             ArtifactHandlerManager artifactHandlerManager,
158             RepositorySystem repoSystem,
159             @Nullable @Named("simple") LocalRepositoryManagerFactory simpleLocalRepoMgrFactory,
160             @Nullable @Named("ide") WorkspaceReader workspaceRepository,
161             SettingsDecrypter settingsDecrypter,
162             EventSpyDispatcher eventSpyDispatcher,
163             MavenRepositorySystem mavenRepositorySystem,
164             RuntimeInformation runtimeInformation) {
165         this.artifactHandlerManager = artifactHandlerManager;
166         this.repoSystem = repoSystem;
167         this.simpleLocalRepoMgrFactory = simpleLocalRepoMgrFactory;
168         this.workspaceRepository = workspaceRepository;
169         this.settingsDecrypter = settingsDecrypter;
170         this.eventSpyDispatcher = eventSpyDispatcher;
171         this.mavenRepositorySystem = mavenRepositorySystem;
172         this.runtimeInformation = runtimeInformation;
173     }
174 
175     @SuppressWarnings("checkstyle:methodLength")
176     public DefaultRepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
177         DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
178         session.setCache(request.getRepositoryCache());
179 
180         Map<Object, Object> configProps = new LinkedHashMap<>();
181         configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
182         configProps.put(ConfigurationProperties.INTERACTIVE, request.isInteractiveMode());
183         configProps.put("maven.startTime", request.getStartTime());
184         // First add properties populated from settings.xml
185         configProps.putAll(getPropertiesFromRequestedProfiles(request));
186         // Resolver's ConfigUtils solely rely on config properties, that is why we need to add both here as well.
187         configProps.putAll(request.getSystemProperties());
188         configProps.putAll(request.getUserProperties());
189 
190         session.setOffline(request.isOffline());
191         session.setChecksumPolicy(request.getGlobalChecksumPolicy());
192         session.setUpdatePolicy(
193                 request.isNoSnapshotUpdates()
194                         ? RepositoryPolicy.UPDATE_POLICY_NEVER
195                         : request.isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null);
196 
197         int errorPolicy = 0;
198         errorPolicy |= request.isCacheNotFound()
199                 ? ResolutionErrorPolicy.CACHE_NOT_FOUND
200                 : ResolutionErrorPolicy.CACHE_DISABLED;
201         errorPolicy |= request.isCacheTransferError()
202                 ? ResolutionErrorPolicy.CACHE_TRANSFER_ERROR
203                 : ResolutionErrorPolicy.CACHE_DISABLED;
204         session.setResolutionErrorPolicy(
205                 new SimpleResolutionErrorPolicy(errorPolicy, errorPolicy | ResolutionErrorPolicy.CACHE_NOT_FOUND));
206 
207         session.setArtifactTypeRegistry(RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager));
208 
209         session.setWorkspaceReader(
210                 request.getWorkspaceReader() != null ? request.getWorkspaceReader() : workspaceRepository);
211 
212         DefaultSettingsDecryptionRequest decrypt = new DefaultSettingsDecryptionRequest();
213         decrypt.setProxies(request.getProxies());
214         decrypt.setServers(request.getServers());
215         SettingsDecryptionResult decrypted = settingsDecrypter.decrypt(decrypt);
216 
217         if (logger.isDebugEnabled()) {
218             for (SettingsProblem problem : decrypted.getProblems()) {
219                 logger.debug(problem.getMessage(), problem.getException());
220             }
221         }
222 
223         DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
224         for (Mirror mirror : request.getMirrors()) {
225             mirrorSelector.add(
226                     mirror.getId(),
227                     mirror.getUrl(),
228                     mirror.getLayout(),
229                     false,
230                     mirror.isBlocked(),
231                     mirror.getMirrorOf(),
232                     mirror.getMirrorOfLayouts());
233         }
234         session.setMirrorSelector(mirrorSelector);
235 
236         DefaultProxySelector proxySelector = new DefaultProxySelector();
237         for (Proxy proxy : decrypted.getProxies()) {
238             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
239             authBuilder.addUsername(proxy.getUsername()).addPassword(proxy.getPassword());
240             proxySelector.add(
241                     new org.eclipse.aether.repository.Proxy(
242                             proxy.getProtocol(), proxy.getHost(), proxy.getPort(), authBuilder.build()),
243                     proxy.getNonProxyHosts());
244         }
245         session.setProxySelector(proxySelector);
246 
247         DefaultAuthenticationSelector authSelector = new DefaultAuthenticationSelector();
248         for (Server server : decrypted.getServers()) {
249             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
250             authBuilder.addUsername(server.getUsername()).addPassword(server.getPassword());
251             authBuilder.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
252             authSelector.add(server.getId(), authBuilder.build());
253 
254             if (server.getConfiguration() != null) {
255                 Dom dom = ((org.codehaus.plexus.util.xml.Xpp3Dom) server.getConfiguration()).getDom();
256                 List<Dom> children = dom.getChildren().stream()
257                         .filter(c -> !"wagonProvider".equals(c.getName()))
258                         .collect(Collectors.toList());
259                 dom = new Xpp3Dom(dom.getName(), null, null, children, null);
260                 PlexusConfiguration config = XmlPlexusConfiguration.toPlexusConfiguration(dom);
261                 configProps.put("aether.connector.wagon.config." + server.getId(), config);
262 
263                 // Translate to proper resolver configuration properties as well (as Plexus XML above is Wagon specific
264                 // only) but support only configuration/httpConfiguration/all, see
265                 // https://maven.apache.org/guides/mini/guide-http-settings.html
266                 Map<String, String> headers = null;
267                 Integer connectTimeout = null;
268                 Integer requestTimeout = null;
269 
270                 PlexusConfiguration httpHeaders = config.getChild("httpHeaders", false);
271                 if (httpHeaders != null) {
272                     PlexusConfiguration[] properties = httpHeaders.getChildren("property");
273                     if (properties != null && properties.length > 0) {
274                         headers = new HashMap<>();
275                         for (PlexusConfiguration property : properties) {
276                             headers.put(
277                                     property.getChild("name").getValue(),
278                                     property.getChild("value").getValue());
279                         }
280                     }
281                 }
282 
283                 PlexusConfiguration connectTimeoutXml = config.getChild("connectTimeout", false);
284                 if (connectTimeoutXml != null) {
285                     connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
286                 } else {
287                     // fallback configuration name
288                     PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
289                     if (httpConfiguration != null) {
290                         PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
291                         if (httpConfigurationAll != null) {
292                             connectTimeoutXml = httpConfigurationAll.getChild("connectionTimeout", false);
293                             if (connectTimeoutXml != null) {
294                                 connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
295                                 logger.warn("Settings for server {} uses legacy format", server.getId());
296                             }
297                         }
298                     }
299                 }
300 
301                 PlexusConfiguration requestTimeoutXml = config.getChild("requestTimeout", false);
302                 if (requestTimeoutXml != null) {
303                     requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
304                 } else {
305                     // fallback configuration name
306                     PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
307                     if (httpConfiguration != null) {
308                         PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
309                         if (httpConfigurationAll != null) {
310                             requestTimeoutXml = httpConfigurationAll.getChild("readTimeout", false);
311                             if (requestTimeoutXml != null) {
312                                 requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
313                                 logger.warn("Settings for server {} uses legacy format", server.getId());
314                             }
315                         }
316                     }
317                 }
318 
319                 // org.eclipse.aether.ConfigurationProperties.HTTP_HEADERS => Map<String, String>
320                 if (headers != null) {
321                     configProps.put(ConfigurationProperties.HTTP_HEADERS + "." + server.getId(), headers);
322                 }
323                 // org.eclipse.aether.ConfigurationProperties.CONNECT_TIMEOUT => int
324                 if (connectTimeout != null) {
325                     configProps.put(ConfigurationProperties.CONNECT_TIMEOUT + "." + server.getId(), connectTimeout);
326                 }
327                 // org.eclipse.aether.ConfigurationProperties.REQUEST_TIMEOUT => int
328                 if (requestTimeout != null) {
329                     configProps.put(ConfigurationProperties.REQUEST_TIMEOUT + "." + server.getId(), requestTimeout);
330                 }
331             }
332 
333             configProps.put("aether.connector.perms.fileMode." + server.getId(), server.getFilePermissions());
334             configProps.put("aether.connector.perms.dirMode." + server.getId(), server.getDirectoryPermissions());
335         }
336         session.setAuthenticationSelector(authSelector);
337 
338         Object transport = configProps.getOrDefault(MAVEN_RESOLVER_TRANSPORT_KEY, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
339         if (MAVEN_RESOLVER_TRANSPORT_DEFAULT.equals(transport)) {
340             // The "default" mode (user did not set anything) from now on defaults to AUTO
341         } else if (MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
342             // Make sure (whatever extra priority is set) that resolver native is selected
343             configProps.put(NATIVE_FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
344             configProps.put(NATIVE_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
345         } else if (MAVEN_RESOLVER_TRANSPORT_WAGON.equals(transport)) {
346             // Make sure (whatever extra priority is set) that wagon is selected
347             configProps.put(WAGON_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
348         } else if (!MAVEN_RESOLVER_TRANSPORT_AUTO.equals(transport)) {
349             throw new IllegalArgumentException("Unknown resolver transport '" + transport
350                     + "'. Supported transports are: " + MAVEN_RESOLVER_TRANSPORT_WAGON + ", "
351                     + MAVEN_RESOLVER_TRANSPORT_NATIVE + ", " + MAVEN_RESOLVER_TRANSPORT_AUTO);
352         }
353 
354         session.setUserProperties(request.getUserProperties());
355         session.setSystemProperties(request.getSystemProperties());
356         session.setConfigProperties(configProps);
357 
358         session.setTransferListener(request.getTransferListener());
359 
360         session.setRepositoryListener(eventSpyDispatcher.chainListener(new LoggingRepositoryListener(logger)));
361 
362         boolean recordReverseTree = ConfigUtils.getBoolean(session, false, MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE);
363         if (recordReverseTree) {
364             session.setRepositoryListener(new ChainedRepositoryListener(
365                     session.getRepositoryListener(), new ReverseTreeRepositoryListener()));
366         }
367 
368         mavenRepositorySystem.injectMirror(request.getRemoteRepositories(), request.getMirrors());
369         mavenRepositorySystem.injectProxy(session, request.getRemoteRepositories());
370         mavenRepositorySystem.injectAuthentication(session, request.getRemoteRepositories());
371 
372         mavenRepositorySystem.injectMirror(request.getPluginArtifactRepositories(), request.getMirrors());
373         mavenRepositorySystem.injectProxy(session, request.getPluginArtifactRepositories());
374         mavenRepositorySystem.injectAuthentication(session, request.getPluginArtifactRepositories());
375 
376         setUpLocalRepositoryManager(request, session);
377 
378         if (Features.buildConsumer(request.getUserProperties()).isActive()) {
379             session.setFileTransformerManager(a -> getTransformersForArtifact(a, session.getData()));
380         }
381 
382         return session;
383     }
384 
385     private void setUpLocalRepositoryManager(MavenExecutionRequest request, DefaultRepositorySystemSession session) {
386         LocalRepository localRepo =
387                 new LocalRepository(request.getLocalRepository().getBasedir());
388 
389         if (request.isUseLegacyLocalRepository()) {
390             try {
391                 session.setLocalRepositoryManager(simpleLocalRepoMgrFactory.newInstance(session, localRepo));
392                 logger.info("Disabling enhanced local repository: using legacy is strongly discouraged to ensure"
393                         + " build reproducibility.");
394             } catch (NoLocalRepositoryManagerException e) {
395                 logger.error("Failed to configure legacy local repository: falling back to default");
396                 session.setLocalRepositoryManager(repoSystem.newLocalRepositoryManager(session, localRepo));
397             }
398         } else {
399             LocalRepositoryManager lrm = repoSystem.newLocalRepositoryManager(session, localRepo);
400 
401             String localRepoTail = ConfigUtils.getString(session, null, MAVEN_REPO_LOCAL_TAIL);
402             if (localRepoTail != null) {
403                 boolean ignoreTailAvailability =
404                         ConfigUtils.getBoolean(session, true, MAVEN_REPO_LOCAL_TAIL_IGNORE_AVAILABILITY);
405                 List<LocalRepositoryManager> tail = new ArrayList<>();
406                 List<String> paths = Arrays.stream(localRepoTail.split(","))
407                         .filter(p -> p != null && !p.trim().isEmpty())
408                         .collect(toList());
409                 for (String path : paths) {
410                     tail.add(repoSystem.newLocalRepositoryManager(session, new LocalRepository(path)));
411                 }
412                 session.setLocalRepositoryManager(new ChainedLocalRepositoryManager(lrm, tail, ignoreTailAvailability));
413             } else {
414                 session.setLocalRepositoryManager(lrm);
415             }
416         }
417     }
418 
419     private Map<?, ?> getPropertiesFromRequestedProfiles(MavenExecutionRequest request) {
420 
421         HashSet<String> activeProfileId =
422                 new HashSet<>(request.getProfileActivation().getRequiredActiveProfileIds());
423         activeProfileId.addAll(request.getProfileActivation().getOptionalActiveProfileIds());
424 
425         return request.getProfiles().stream()
426                 .filter(profile -> activeProfileId.contains(profile.getId()))
427                 .map(ModelBase::getProperties)
428                 .flatMap(properties -> properties.entrySet().stream())
429                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k2));
430     }
431 
432     private String getUserAgent() {
433         String version = runtimeInformation.getMavenVersion();
434         version = version.isEmpty() ? version : "/" + version;
435         return "Apache-Maven" + version + " (Java " + System.getProperty("java.version") + "; "
436                 + System.getProperty("os.name") + " " + System.getProperty("os.version") + ")";
437     }
438 
439     private Collection<FileTransformer> getTransformersForArtifact(
440             final Artifact artifact, final SessionData sessionData) {
441         TransformerContext context = (TransformerContext) sessionData.get(TransformerContext.KEY);
442         Collection<FileTransformer> transformers = new ArrayList<>();
443 
444         // In case of install:install-file there's no transformer context, as the goal is unrelated to the lifecycle.
445         if ("pom".equals(artifact.getExtension()) && context != null) {
446             transformers.add(new FileTransformer() {
447                 @Override
448                 public InputStream transformData(File pomFile) throws IOException, TransformException {
449                     try {
450                         return new ConsumerModelSourceTransformer().transform(pomFile.toPath(), context);
451                     } catch (XmlPullParserException e) {
452                         throw new TransformException(e);
453                     }
454                 }
455 
456                 @Override
457                 public Artifact transformArtifact(Artifact artifact) {
458                     return artifact;
459                 }
460             });
461         }
462         return Collections.unmodifiableCollection(transformers);
463     }
464 }