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