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