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 java.nio.file.Path;
22  import java.nio.file.Paths;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.function.Predicate;
31  import java.util.stream.Collectors;
32  
33  import org.apache.maven.api.Constants;
34  import org.apache.maven.api.di.Inject;
35  import org.apache.maven.api.di.Named;
36  import org.apache.maven.api.di.Singleton;
37  import org.apache.maven.api.services.TypeRegistry;
38  import org.apache.maven.api.xml.XmlNode;
39  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
40  import org.apache.maven.execution.MavenExecutionRequest;
41  import org.apache.maven.internal.impl.resolver.MavenSessionBuilderSupplier;
42  import org.apache.maven.internal.xml.XmlNodeImpl;
43  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
44  import org.apache.maven.model.ModelBase;
45  import org.apache.maven.resolver.RepositorySystemSessionFactory;
46  import org.apache.maven.rtinfo.RuntimeInformation;
47  import org.apache.maven.settings.Mirror;
48  import org.apache.maven.settings.Proxy;
49  import org.apache.maven.settings.Server;
50  import org.apache.maven.settings.building.SettingsProblem;
51  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
52  import org.apache.maven.settings.crypto.SettingsDecrypter;
53  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
54  import org.codehaus.plexus.configuration.PlexusConfiguration;
55  import org.eclipse.aether.ConfigurationProperties;
56  import org.eclipse.aether.RepositoryListener;
57  import org.eclipse.aether.RepositorySystem;
58  import org.eclipse.aether.RepositorySystemSession;
59  import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
60  import org.eclipse.aether.artifact.Artifact;
61  import org.eclipse.aether.artifact.DefaultArtifact;
62  import org.eclipse.aether.collection.VersionFilter;
63  import org.eclipse.aether.repository.RepositoryPolicy;
64  import org.eclipse.aether.resolution.ResolutionErrorPolicy;
65  import org.eclipse.aether.util.graph.version.ChainedVersionFilter;
66  import org.eclipse.aether.util.graph.version.ContextualSnapshotVersionFilter;
67  import org.eclipse.aether.util.graph.version.HighestVersionFilter;
68  import org.eclipse.aether.util.graph.version.LowestVersionFilter;
69  import org.eclipse.aether.util.graph.version.PredicateVersionFilter;
70  import org.eclipse.aether.util.listener.ChainedRepositoryListener;
71  import org.eclipse.aether.util.repository.AuthenticationBuilder;
72  import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
73  import org.eclipse.aether.util.repository.DefaultMirrorSelector;
74  import org.eclipse.aether.util.repository.DefaultProxySelector;
75  import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
76  import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy;
77  import org.eclipse.aether.version.InvalidVersionSpecificationException;
78  import org.eclipse.aether.version.Version;
79  import org.eclipse.aether.version.VersionRange;
80  import org.eclipse.aether.version.VersionScheme;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  import static java.util.Objects.requireNonNull;
85  
86  /**
87   * @since 3.3.0
88   */
89  @Named
90  @Singleton
91  public class DefaultRepositorySystemSessionFactory implements RepositorySystemSessionFactory {
92  
93      public static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
94  
95      public static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
96  
97      public static final String MAVEN_RESOLVER_TRANSPORT_APACHE = "apache";
98  
99      public static final String MAVEN_RESOLVER_TRANSPORT_JDK = "jdk";
100 
101     /**
102      * This name for Apache HttpClient transport is deprecated.
103      *
104      * @deprecated Renamed to {@link #MAVEN_RESOLVER_TRANSPORT_APACHE}
105      */
106     @Deprecated
107     private static final String MAVEN_RESOLVER_TRANSPORT_NATIVE = "native";
108 
109     public static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
110 
111     private static final String WAGON_TRANSPORTER_PRIORITY_KEY = "aether.priority.WagonTransporterFactory";
112 
113     private static final String APACHE_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.ApacheTransporterFactory";
114 
115     private static final String JDK_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.JdkTransporterFactory";
116 
117     private static final String FILE_TRANSPORTER_PRIORITY_KEY = "aether.priority.FileTransporterFactory";
118 
119     private static final String RESOLVER_MAX_PRIORITY = String.valueOf(Float.MAX_VALUE);
120 
121     private final Logger logger = LoggerFactory.getLogger(getClass());
122 
123     private final RepositorySystem repoSystem;
124 
125     private final SettingsDecrypter settingsDecrypter;
126 
127     private final EventSpyDispatcher eventSpyDispatcher;
128 
129     private final RuntimeInformation runtimeInformation;
130 
131     private final TypeRegistry typeRegistry;
132 
133     private final VersionScheme versionScheme;
134 
135     private final Map<String, MavenExecutionRequestExtender> requestExtenders;
136 
137     private final Map<String, RepositorySystemSessionExtender> sessionExtenders;
138 
139     @SuppressWarnings("checkstyle:ParameterNumber")
140     @Inject
141     DefaultRepositorySystemSessionFactory(
142             RepositorySystem repoSystem,
143             SettingsDecrypter settingsDecrypter,
144             EventSpyDispatcher eventSpyDispatcher,
145             RuntimeInformation runtimeInformation,
146             TypeRegistry typeRegistry,
147             VersionScheme versionScheme,
148             Map<String, MavenExecutionRequestExtender> requestExtenders,
149             Map<String, RepositorySystemSessionExtender> sessionExtenders) {
150         this.repoSystem = repoSystem;
151         this.settingsDecrypter = settingsDecrypter;
152         this.eventSpyDispatcher = eventSpyDispatcher;
153         this.runtimeInformation = runtimeInformation;
154         this.typeRegistry = typeRegistry;
155         this.versionScheme = versionScheme;
156         this.requestExtenders = requestExtenders;
157         this.sessionExtenders = sessionExtenders;
158     }
159 
160     @Deprecated
161     public RepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
162         return newRepositorySessionBuilder(request).build();
163     }
164 
165     @SuppressWarnings({"checkstyle:methodLength"})
166     public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) {
167         requireNonNull(request, "request");
168 
169         // apply MavenExecutionRequestExtenders
170         for (MavenExecutionRequestExtender requestExtender : requestExtenders.values()) {
171             requestExtender.extend(request);
172         }
173 
174         MavenSessionBuilderSupplier supplier = new MavenSessionBuilderSupplier(repoSystem);
175         SessionBuilder sessionBuilder = supplier.get();
176         sessionBuilder.setArtifactTypeRegistry(new TypeRegistryAdapter(typeRegistry)); // dynamic
177         sessionBuilder.setCache(request.getRepositoryCache());
178 
179         // this map is read ONLY to get config from (profiles + env + system + user)
180         Map<String, String> mergedProps = createMergedProperties(request);
181 
182         // configProps map is kept "pristine", is written ONLY, the mandatory resolver config
183         Map<String, Object> configProps = new LinkedHashMap<>();
184         configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
185         configProps.put(ConfigurationProperties.INTERACTIVE, request.isInteractiveMode());
186         configProps.put("maven.startTime", request.getStartTime());
187 
188         sessionBuilder.setOffline(request.isOffline());
189         sessionBuilder.setChecksumPolicy(request.getGlobalChecksumPolicy());
190         sessionBuilder.setUpdatePolicy(
191                 request.isNoSnapshotUpdates()
192                         ? RepositoryPolicy.UPDATE_POLICY_NEVER
193                         : request.isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null);
194 
195         int errorPolicy = 0;
196         errorPolicy |= request.isCacheNotFound()
197                 ? ResolutionErrorPolicy.CACHE_NOT_FOUND
198                 : ResolutionErrorPolicy.CACHE_DISABLED;
199         errorPolicy |= request.isCacheTransferError()
200                 ? ResolutionErrorPolicy.CACHE_TRANSFER_ERROR
201                 : ResolutionErrorPolicy.CACHE_DISABLED;
202         sessionBuilder.setResolutionErrorPolicy(
203                 new SimpleResolutionErrorPolicy(errorPolicy, errorPolicy | ResolutionErrorPolicy.CACHE_NOT_FOUND));
204 
205         sessionBuilder.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(
206                 request.isIgnoreMissingArtifactDescriptor(), request.isIgnoreInvalidArtifactDescriptor()));
207 
208         VersionFilter versionFilter = buildVersionFilter(mergedProps.get(Constants.MAVEN_VERSION_FILTERS));
209         if (versionFilter != null) {
210             sessionBuilder.setVersionFilter(versionFilter);
211         }
212 
213         DefaultSettingsDecryptionRequest decrypt = new DefaultSettingsDecryptionRequest();
214         decrypt.setProxies(request.getProxies());
215         decrypt.setServers(request.getServers());
216         SettingsDecryptionResult decrypted = settingsDecrypter.decrypt(decrypt);
217         for (SettingsProblem problem : decrypted.getProblems()) {
218             if (problem.getSeverity() == SettingsProblem.Severity.WARNING) {
219                 logger.warn(problem.getMessage());
220             } else if (problem.getSeverity() == SettingsProblem.Severity.ERROR) {
221                 logger.error(
222                         problem.getMessage(),
223                         request.isShowErrors()
224                                 ? problem.getException()
225                                 : problem.getException().getMessage());
226             }
227         }
228 
229         DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
230         for (Mirror mirror : request.getMirrors()) {
231             mirrorSelector.add(
232                     mirror.getId(),
233                     mirror.getUrl(),
234                     mirror.getLayout(),
235                     false,
236                     mirror.isBlocked(),
237                     mirror.getMirrorOf(),
238                     mirror.getMirrorOfLayouts());
239         }
240         sessionBuilder.setMirrorSelector(mirrorSelector);
241 
242         DefaultProxySelector proxySelector = new DefaultProxySelector();
243         for (Proxy proxy : decrypted.getProxies()) {
244             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
245             authBuilder.addUsername(proxy.getUsername()).addPassword(proxy.getPassword());
246             proxySelector.add(
247                     new org.eclipse.aether.repository.Proxy(
248                             proxy.getProtocol(), proxy.getHost(), proxy.getPort(), authBuilder.build()),
249                     proxy.getNonProxyHosts());
250         }
251         sessionBuilder.setProxySelector(proxySelector);
252 
253         // Note: we do NOT use WagonTransportConfigurationKeys here as Maven Core does NOT depend on Wagon Transport
254         // and this is okay and "good thing".
255         DefaultAuthenticationSelector authSelector = new DefaultAuthenticationSelector();
256         for (Server server : decrypted.getServers()) {
257             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
258             authBuilder.addUsername(server.getUsername()).addPassword(server.getPassword());
259             authBuilder.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
260             authSelector.add(server.getId(), authBuilder.build());
261 
262             if (server.getConfiguration() != null) {
263                 XmlNode dom = server.getDelegate().getConfiguration();
264                 List<XmlNode> children = dom.getChildren().stream()
265                         .filter(c -> !"wagonProvider".equals(c.getName()))
266                         .collect(Collectors.toList());
267                 dom = new XmlNodeImpl(dom.getName(), null, null, children, null);
268                 PlexusConfiguration config = XmlPlexusConfiguration.toPlexusConfiguration(dom);
269                 configProps.put("aether.transport.wagon.config." + server.getId(), config);
270 
271                 // Translate to proper resolver configuration properties as well (as Plexus XML above is Wagon specific
272                 // only) but support only configuration/httpConfiguration/all, see
273                 // https://maven.apache.org/guides/mini/guide-http-settings.html
274                 Map<String, String> headers = null;
275                 Integer connectTimeout = null;
276                 Integer requestTimeout = null;
277 
278                 PlexusConfiguration httpHeaders = config.getChild("httpHeaders", false);
279                 if (httpHeaders != null) {
280                     PlexusConfiguration[] properties = httpHeaders.getChildren("property");
281                     if (properties != null && properties.length > 0) {
282                         headers = new HashMap<>();
283                         for (PlexusConfiguration property : properties) {
284                             headers.put(
285                                     property.getChild("name").getValue(),
286                                     property.getChild("value").getValue());
287                         }
288                     }
289                 }
290 
291                 PlexusConfiguration connectTimeoutXml = config.getChild("connectTimeout", false);
292                 if (connectTimeoutXml != null) {
293                     connectTimeout = Integer.parseInt(connectTimeoutXml.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                             connectTimeoutXml = httpConfigurationAll.getChild("connectionTimeout", false);
301                             if (connectTimeoutXml != null) {
302                                 connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
303                                 logger.warn("Settings for server {} uses legacy format", server.getId());
304                             }
305                         }
306                     }
307                 }
308 
309                 PlexusConfiguration requestTimeoutXml = config.getChild("requestTimeout", false);
310                 if (requestTimeoutXml != null) {
311                     requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
312                 } else {
313                     // fallback configuration name
314                     PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
315                     if (httpConfiguration != null) {
316                         PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
317                         if (httpConfigurationAll != null) {
318                             requestTimeoutXml = httpConfigurationAll.getChild("readTimeout", false);
319                             if (requestTimeoutXml != null) {
320                                 requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
321                                 logger.warn("Settings for server {} uses legacy format", server.getId());
322                             }
323                         }
324                     }
325                 }
326 
327                 // org.eclipse.aether.ConfigurationProperties.HTTP_HEADERS => Map<String, String>
328                 if (headers != null) {
329                     configProps.put(ConfigurationProperties.HTTP_HEADERS + "." + server.getId(), headers);
330                 }
331                 // org.eclipse.aether.ConfigurationProperties.CONNECT_TIMEOUT => int
332                 if (connectTimeout != null) {
333                     configProps.put(ConfigurationProperties.CONNECT_TIMEOUT + "." + server.getId(), connectTimeout);
334                 }
335                 // org.eclipse.aether.ConfigurationProperties.REQUEST_TIMEOUT => int
336                 if (requestTimeout != null) {
337                     configProps.put(ConfigurationProperties.REQUEST_TIMEOUT + "." + server.getId(), requestTimeout);
338                 }
339             }
340 
341             configProps.put("aether.transport.wagon.perms.fileMode." + server.getId(), server.getFilePermissions());
342             configProps.put("aether.transport.wagon.perms.dirMode." + server.getId(), server.getDirectoryPermissions());
343         }
344         sessionBuilder.setAuthenticationSelector(authSelector);
345 
346         Object transport =
347                 mergedProps.getOrDefault(Constants.MAVEN_RESOLVER_TRANSPORT, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
348         if (MAVEN_RESOLVER_TRANSPORT_DEFAULT.equals(transport)) {
349             // The "default" mode (user did not set anything) from now on defaults to AUTO
350         } else if (MAVEN_RESOLVER_TRANSPORT_JDK.equals(transport)) {
351             // Make sure (whatever extra priority is set) that resolver file/jdk is selected
352             configProps.put(FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
353             configProps.put(JDK_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
354         } else if (MAVEN_RESOLVER_TRANSPORT_APACHE.equals(transport)
355                 || MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
356             if (MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
357                 logger.warn(
358                         "Transport name '{}' is DEPRECATED/RENAMED, use '{}' instead",
359                         MAVEN_RESOLVER_TRANSPORT_NATIVE,
360                         MAVEN_RESOLVER_TRANSPORT_APACHE);
361             }
362             // Make sure (whatever extra priority is set) that resolver file/apache is selected
363             configProps.put(FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
364             configProps.put(APACHE_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
365         } else if (MAVEN_RESOLVER_TRANSPORT_WAGON.equals(transport)) {
366             // Make sure (whatever extra priority is set) that wagon is selected
367             configProps.put(WAGON_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
368         } else if (!MAVEN_RESOLVER_TRANSPORT_AUTO.equals(transport)) {
369             throw new IllegalArgumentException("Unknown resolver transport '" + transport
370                     + "'. Supported transports are: " + MAVEN_RESOLVER_TRANSPORT_WAGON + ", "
371                     + MAVEN_RESOLVER_TRANSPORT_APACHE + ", " + MAVEN_RESOLVER_TRANSPORT_JDK + ", "
372                     + MAVEN_RESOLVER_TRANSPORT_AUTO);
373         }
374 
375         sessionBuilder.setIgnoreArtifactDescriptorRepositories(request.isIgnoreTransitiveRepositories());
376 
377         sessionBuilder.setTransferListener(request.getTransferListener());
378 
379         RepositoryListener repositoryListener = eventSpyDispatcher.chainListener(new LoggingRepositoryListener(logger));
380 
381         boolean recordReverseTree = Boolean.parseBoolean(
382                 mergedProps.getOrDefault(Constants.MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE, Boolean.FALSE.toString()));
383         if (recordReverseTree) {
384             repositoryListener = new ChainedRepositoryListener(repositoryListener, new ReverseTreeRepositoryListener());
385         }
386         sessionBuilder.setRepositoryListener(repositoryListener);
387 
388         // may be overridden
389         String resolverDependencyManagerTransitivity = mergedProps.getOrDefault(
390                 Constants.MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY, Boolean.TRUE.toString());
391         sessionBuilder.setDependencyManager(
392                 supplier.getDependencyManager(Boolean.parseBoolean(resolverDependencyManagerTransitivity)));
393 
394         ArrayList<Path> paths = new ArrayList<>();
395         paths.add(Paths.get(request.getLocalRepository().getBasedir()));
396         String localRepoTail = mergedProps.get(Constants.MAVEN_REPO_LOCAL_TAIL);
397         if (localRepoTail != null) {
398             Arrays.stream(localRepoTail.split(","))
399                     .filter(p -> p != null && !p.trim().isEmpty())
400                     .map(Paths::get)
401                     .forEach(paths::add);
402         }
403         sessionBuilder.withLocalRepositoryBaseDirectories(paths);
404 
405         for (RepositorySystemSessionExtender extender : sessionExtenders.values()) {
406             extender.extend(request, configProps, mirrorSelector, proxySelector, authSelector);
407         }
408 
409         // at this point we have "config" with pure MANDATORY resolver config, so resolver final config properties are
410         // mergedProperties + configProperties
411         HashMap<String, Object> finalConfigProperties = new HashMap<>();
412         finalConfigProperties.putAll(mergedProps);
413         finalConfigProperties.putAll(configProps);
414 
415         sessionBuilder.setUserProperties(request.getUserProperties());
416         sessionBuilder.setSystemProperties(request.getSystemProperties());
417         sessionBuilder.setConfigProperties(finalConfigProperties);
418 
419         return sessionBuilder;
420     }
421 
422     private VersionFilter buildVersionFilter(String filterExpression) {
423         ArrayList<VersionFilter> filters = new ArrayList<>();
424         if (filterExpression != null) {
425             List<String> expressions = Arrays.stream(filterExpression.split(";"))
426                     .filter(s -> s != null && !s.trim().isEmpty())
427                     .toList();
428             for (String expression : expressions) {
429                 if ("h".equals(expression)) {
430                     filters.add(new HighestVersionFilter());
431                 } else if (expression.startsWith("h(") && expression.endsWith(")")) {
432                     int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
433                     filters.add(new HighestVersionFilter(num));
434                 } else if ("l".equals(expression)) {
435                     filters.add(new LowestVersionFilter());
436                 } else if (expression.startsWith("l(") && expression.endsWith(")")) {
437                     int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
438                     filters.add(new LowestVersionFilter(num));
439                 } else if ("s".equals(expression)) {
440                     filters.add(new ContextualSnapshotVersionFilter());
441                 } else if (expression.startsWith("e(") && expression.endsWith(")")) {
442                     Artifact artifact = new DefaultArtifact(expression.substring(2, expression.length() - 1));
443                     VersionRange versionRange =
444                             artifact.getVersion().contains(",") ? parseVersionRange(artifact.getVersion()) : null;
445                     Predicate<Artifact> predicate = a -> {
446                         if (artifact.getGroupId().equals(a.getGroupId())
447                                 && artifact.getArtifactId().equals(a.getArtifactId())) {
448                             if (versionRange != null) {
449                                 Version v = parseVersion(a.getVersion());
450                                 return !versionRange.containsVersion(v);
451                             } else {
452                                 return !artifact.getVersion().equals(a.getVersion());
453                             }
454                         }
455                         return true;
456                     };
457                     filters.add(new PredicateVersionFilter(predicate));
458                 } else {
459                     throw new IllegalArgumentException("Unsupported filter expression: " + expression);
460                 }
461             }
462         }
463         if (filters.isEmpty()) {
464             return null;
465         } else if (filters.size() == 1) {
466             return filters.get(0);
467         } else {
468             return ChainedVersionFilter.newInstance(filters);
469         }
470     }
471 
472     private Version parseVersion(String spec) {
473         try {
474             return versionScheme.parseVersion(spec);
475         } catch (InvalidVersionSpecificationException e) {
476             throw new RuntimeException(e);
477         }
478     }
479 
480     private VersionRange parseVersionRange(String spec) {
481         try {
482             return versionScheme.parseVersionRange(spec);
483         } catch (InvalidVersionSpecificationException e) {
484             throw new RuntimeException(e);
485         }
486     }
487 
488     @SuppressWarnings({"unchecked", "rawtypes"})
489     private Map<String, String> createMergedProperties(MavenExecutionRequest request) {
490         // this throwaway map is really ONLY to get config from (profiles + env + system + user)
491         Map<String, String> mergedProps = new HashMap<>();
492         mergedProps.putAll(getPropertiesFromRequestedProfiles(request));
493         mergedProps.putAll(new HashMap<String, String>((Map) request.getSystemProperties()));
494         mergedProps.putAll(new HashMap<String, String>((Map) request.getUserProperties()));
495         return mergedProps;
496     }
497 
498     private Map<String, String> getPropertiesFromRequestedProfiles(MavenExecutionRequest request) {
499         HashSet<String> activeProfileId =
500                 new HashSet<>(request.getProfileActivation().getRequiredActiveProfileIds());
501         activeProfileId.addAll(request.getProfileActivation().getOptionalActiveProfileIds());
502 
503         return request.getProfiles().stream()
504                 .filter(profile -> activeProfileId.contains(profile.getId()))
505                 .map(ModelBase::getProperties)
506                 .flatMap(properties -> properties.entrySet().stream())
507                 .filter(e -> e.getValue() != null)
508                 .collect(Collectors.toMap(
509                         e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue()), (k1, k2) -> k2));
510     }
511 
512     private String getUserAgent() {
513         String version = runtimeInformation.getMavenVersion();
514         version = version.isEmpty() ? version : "/" + version;
515         return "Apache-Maven" + version + " (Java " + System.getProperty("java.version") + "; "
516                 + System.getProperty("os.name") + " " + System.getProperty("os.version") + ")";
517     }
518 }