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