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