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