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.io.File;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.LinkedHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.function.Predicate;
34  import java.util.stream.Collectors;
35  
36  import org.apache.maven.RepositoryUtils;
37  import org.apache.maven.api.services.TypeRegistry;
38  import org.apache.maven.api.xml.XmlNode;
39  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
40  import org.apache.maven.artifact.repository.ArtifactRepository;
41  import org.apache.maven.artifact.repository.Authentication;
42  import org.apache.maven.bridge.MavenRepositorySystem;
43  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
44  import org.apache.maven.execution.MavenExecutionRequest;
45  import org.apache.maven.internal.xml.XmlNodeImpl;
46  import org.apache.maven.internal.xml.XmlPlexusConfiguration;
47  import org.apache.maven.model.ModelBase;
48  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
49  import org.apache.maven.rtinfo.RuntimeInformation;
50  import org.apache.maven.settings.Mirror;
51  import org.apache.maven.settings.Proxy;
52  import org.apache.maven.settings.Server;
53  import org.apache.maven.settings.building.SettingsProblem;
54  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
55  import org.apache.maven.settings.crypto.SettingsDecrypter;
56  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
57  import org.codehaus.plexus.configuration.PlexusConfiguration;
58  import org.eclipse.aether.ConfigurationProperties;
59  import org.eclipse.aether.RepositoryListener;
60  import org.eclipse.aether.RepositorySystem;
61  import org.eclipse.aether.RepositorySystemSession;
62  import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
63  import org.eclipse.aether.artifact.Artifact;
64  import org.eclipse.aether.artifact.DefaultArtifact;
65  import org.eclipse.aether.collection.VersionFilter;
66  import org.eclipse.aether.repository.AuthenticationContext;
67  import org.eclipse.aether.repository.AuthenticationSelector;
68  import org.eclipse.aether.repository.ProxySelector;
69  import org.eclipse.aether.repository.RemoteRepository;
70  import org.eclipse.aether.repository.RepositoryPolicy;
71  import org.eclipse.aether.repository.WorkspaceReader;
72  import org.eclipse.aether.resolution.ResolutionErrorPolicy;
73  import org.eclipse.aether.util.graph.manager.ClassicDependencyManager;
74  import org.eclipse.aether.util.graph.version.*;
75  import org.eclipse.aether.util.listener.ChainedRepositoryListener;
76  import org.eclipse.aether.util.repository.AuthenticationBuilder;
77  import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
78  import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
79  import org.eclipse.aether.util.repository.DefaultMirrorSelector;
80  import org.eclipse.aether.util.repository.DefaultProxySelector;
81  import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
82  import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy;
83  import org.eclipse.aether.version.InvalidVersionSpecificationException;
84  import org.eclipse.aether.version.Version;
85  import org.eclipse.aether.version.VersionRange;
86  import org.eclipse.aether.version.VersionScheme;
87  import org.eclipse.sisu.Nullable;
88  import org.slf4j.Logger;
89  import org.slf4j.LoggerFactory;
90  
91  /**
92   * @since 3.3.0
93   */
94  @Named
95  public class DefaultRepositorySystemSessionFactory {
96      /**
97       * User property for version filters expression, a semicolon separated list of filters to apply. By default, no version
98       * filter is applied (like in Maven 3).
99       * <p>
100      * Supported filters:
101      * <ul>
102      *     <li>"h" or "h(num)" - highest version or top list of highest ones filter</li>
103      *     <li>"l" or "l(num)" - lowest version or bottom list of lowest ones filter</li>
104      *     <li>"s" - contextual snapshot filter</li>
105      *     <li>"e(G:A:V)" - predicate filter (leaves out G:A:V from range, if hit, V can be range)</li>
106      * </ul>
107      * Example filter expression: {@code "h(5);s;e(org.foo:bar:1)} will cause: ranges are filtered for "top 5" (instead
108      * full range), snapshots are banned if root project is not a snapshot, and if range for {@code org.foo:bar} is
109      * being processed, version 1 is omitted.
110      *
111      * @since 4.0.0
112      */
113     private static final String MAVEN_VERSION_FILTERS = "maven.versionFilters";
114 
115     /**
116      * User property for chained LRM: list of "tail" local repository paths (separated by comma), to be used with
117      * {@link ChainedLocalRepositoryManager}.
118      * Default value: {@code null}, no chained LRM is used.
119      *
120      * @since 3.9.0
121      */
122     private static final String MAVEN_REPO_LOCAL_TAIL = "maven.repo.local.tail";
123 
124     /**
125      * User property for reverse dependency tree. If enabled, Maven will record ".tracking" directory into local
126      * repository with "reverse dependency tree", essentially explaining WHY given artifact is present in local
127      * repository.
128      * Default: {@code false}, will not record anything.
129      *
130      * @since 3.9.0
131      */
132     private static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree";
133 
134     /**
135      * User property for selecting dependency manager behaviour regarding transitive dependencies and dependency
136      * management entries in their POMs. Maven 3 targeted full backward compatibility with Maven2, hence it ignored
137      * dependency management entries in transitive dependency POMs. Maven 4 enables "transitivity" by default, hence
138      * unlike Maven2, obeys dependency management entries deep in dependency graph as well.
139      * <p>
140      * Default: {@code "true"}.
141      *
142      * @since 4.0.0
143      */
144     private static final String MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY_KEY =
145             "maven.resolver.dependencyManagerTransitivity";
146 
147     private static final String MAVEN_RESOLVER_TRANSPORT_KEY = "maven.resolver.transport";
148 
149     private static final String MAVEN_RESOLVER_TRANSPORT_DEFAULT = "default";
150 
151     private static final String MAVEN_RESOLVER_TRANSPORT_WAGON = "wagon";
152 
153     private static final String MAVEN_RESOLVER_TRANSPORT_APACHE = "apache";
154 
155     private static final String MAVEN_RESOLVER_TRANSPORT_JDK = "jdk";
156 
157     /**
158      * This name for Apache HttpClient transport is deprecated.
159      *
160      * @deprecated Renamed to {@link #MAVEN_RESOLVER_TRANSPORT_APACHE}
161      */
162     @Deprecated
163     private static final String MAVEN_RESOLVER_TRANSPORT_NATIVE = "native";
164 
165     private static final String MAVEN_RESOLVER_TRANSPORT_AUTO = "auto";
166 
167     private static final String WAGON_TRANSPORTER_PRIORITY_KEY = "aether.priority.WagonTransporterFactory";
168 
169     private static final String APACHE_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.ApacheTransporterFactory";
170 
171     private static final String JDK_HTTP_TRANSPORTER_PRIORITY_KEY = "aether.priority.JdkTransporterFactory";
172 
173     private static final String FILE_TRANSPORTER_PRIORITY_KEY = "aether.priority.FileTransporterFactory";
174 
175     private static final String RESOLVER_MAX_PRIORITY = String.valueOf(Float.MAX_VALUE);
176 
177     private final Logger logger = LoggerFactory.getLogger(getClass());
178 
179     private final ArtifactHandlerManager artifactHandlerManager;
180 
181     private final RepositorySystem repoSystem;
182 
183     private final WorkspaceReader workspaceRepository;
184 
185     private final SettingsDecrypter settingsDecrypter;
186 
187     private final EventSpyDispatcher eventSpyDispatcher;
188 
189     private final RuntimeInformation runtimeInformation;
190 
191     private final TypeRegistry typeRegistry;
192 
193     private final VersionScheme versionScheme;
194 
195     @SuppressWarnings("checkstyle:ParameterNumber")
196     @Inject
197     public DefaultRepositorySystemSessionFactory(
198             ArtifactHandlerManager artifactHandlerManager,
199             RepositorySystem repoSystem,
200             @Nullable @Named("ide") WorkspaceReader workspaceRepository,
201             SettingsDecrypter settingsDecrypter,
202             EventSpyDispatcher eventSpyDispatcher,
203             RuntimeInformation runtimeInformation,
204             TypeRegistry typeRegistry,
205             VersionScheme versionScheme) {
206         this.artifactHandlerManager = artifactHandlerManager;
207         this.repoSystem = repoSystem;
208         this.workspaceRepository = workspaceRepository;
209         this.settingsDecrypter = settingsDecrypter;
210         this.eventSpyDispatcher = eventSpyDispatcher;
211         this.runtimeInformation = runtimeInformation;
212         this.typeRegistry = typeRegistry;
213         this.versionScheme = versionScheme;
214     }
215 
216     @Deprecated
217     public RepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
218         return newRepositorySessionBuilder(request).build();
219     }
220 
221     @SuppressWarnings("checkstyle:methodLength")
222     public SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) {
223         SessionBuilder session = MavenRepositorySystemUtils.newSession(
224                 repoSystem.createSessionBuilder(), new TypeRegistryAdapter(typeRegistry));
225         session.setCache(request.getRepositoryCache());
226 
227         Map<Object, Object> configProps = new LinkedHashMap<>();
228         configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
229         configProps.put(ConfigurationProperties.INTERACTIVE, request.isInteractiveMode());
230         configProps.put("maven.startTime", request.getStartTime());
231         // First add properties populated from settings.xml
232         configProps.putAll(getPropertiesFromRequestedProfiles(request));
233         // Resolver's ConfigUtils solely rely on config properties, that is why we need to add both here as well.
234         configProps.putAll(request.getSystemProperties());
235         configProps.putAll(request.getUserProperties());
236 
237         session.setOffline(request.isOffline());
238         session.setChecksumPolicy(request.getGlobalChecksumPolicy());
239         session.setUpdatePolicy(
240                 request.isNoSnapshotUpdates()
241                         ? RepositoryPolicy.UPDATE_POLICY_NEVER
242                         : request.isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null);
243 
244         int errorPolicy = 0;
245         errorPolicy |= request.isCacheNotFound()
246                 ? ResolutionErrorPolicy.CACHE_NOT_FOUND
247                 : ResolutionErrorPolicy.CACHE_DISABLED;
248         errorPolicy |= request.isCacheTransferError()
249                 ? ResolutionErrorPolicy.CACHE_TRANSFER_ERROR
250                 : ResolutionErrorPolicy.CACHE_DISABLED;
251         session.setResolutionErrorPolicy(
252                 new SimpleResolutionErrorPolicy(errorPolicy, errorPolicy | ResolutionErrorPolicy.CACHE_NOT_FOUND));
253 
254         session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(
255                 request.isIgnoreMissingArtifactDescriptor(), request.isIgnoreInvalidArtifactDescriptor()));
256 
257         VersionFilter versionFilter = buildVersionFilter((String) configProps.get(MAVEN_VERSION_FILTERS));
258         if (versionFilter != null) {
259             session.setVersionFilter(versionFilter);
260         }
261 
262         session.setArtifactTypeRegistry(RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager));
263 
264         session.setWorkspaceReader(
265                 request.getWorkspaceReader() != null ? request.getWorkspaceReader() : workspaceRepository);
266 
267         DefaultSettingsDecryptionRequest decrypt = new DefaultSettingsDecryptionRequest();
268         decrypt.setProxies(request.getProxies());
269         decrypt.setServers(request.getServers());
270         SettingsDecryptionResult decrypted = settingsDecrypter.decrypt(decrypt);
271 
272         if (logger.isDebugEnabled()) {
273             for (SettingsProblem problem : decrypted.getProblems()) {
274                 logger.debug(problem.getMessage(), problem.getException());
275             }
276         }
277 
278         DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
279         for (Mirror mirror : request.getMirrors()) {
280             mirrorSelector.add(
281                     mirror.getId(),
282                     mirror.getUrl(),
283                     mirror.getLayout(),
284                     false,
285                     mirror.isBlocked(),
286                     mirror.getMirrorOf(),
287                     mirror.getMirrorOfLayouts());
288         }
289         session.setMirrorSelector(mirrorSelector);
290 
291         DefaultProxySelector proxySelector = new DefaultProxySelector();
292         for (Proxy proxy : decrypted.getProxies()) {
293             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
294             authBuilder.addUsername(proxy.getUsername()).addPassword(proxy.getPassword());
295             proxySelector.add(
296                     new org.eclipse.aether.repository.Proxy(
297                             proxy.getProtocol(), proxy.getHost(), proxy.getPort(), authBuilder.build()),
298                     proxy.getNonProxyHosts());
299         }
300         session.setProxySelector(proxySelector);
301 
302         // Note: we do NOT use WagonTransportConfigurationKeys here as Maven Core does NOT depend on Wagon Transport
303         // and this is okay and "good thing".
304         DefaultAuthenticationSelector authSelector = new DefaultAuthenticationSelector();
305         for (Server server : decrypted.getServers()) {
306             AuthenticationBuilder authBuilder = new AuthenticationBuilder();
307             authBuilder.addUsername(server.getUsername()).addPassword(server.getPassword());
308             authBuilder.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
309             authSelector.add(server.getId(), authBuilder.build());
310 
311             if (server.getConfiguration() != null) {
312                 XmlNode dom = server.getDelegate().getConfiguration();
313                 List<XmlNode> children = dom.getChildren().stream()
314                         .filter(c -> !"wagonProvider".equals(c.getName()))
315                         .collect(Collectors.toList());
316                 dom = new XmlNodeImpl(dom.getName(), null, null, children, null);
317                 PlexusConfiguration config = XmlPlexusConfiguration.toPlexusConfiguration(dom);
318                 configProps.put("aether.transport.wagon.config." + server.getId(), config);
319 
320                 // Translate to proper resolver configuration properties as well (as Plexus XML above is Wagon specific
321                 // only) but support only configuration/httpConfiguration/all, see
322                 // https://maven.apache.org/guides/mini/guide-http-settings.html
323                 Map<String, String> headers = null;
324                 Integer connectTimeout = null;
325                 Integer requestTimeout = null;
326 
327                 PlexusConfiguration httpHeaders = config.getChild("httpHeaders", false);
328                 if (httpHeaders != null) {
329                     PlexusConfiguration[] properties = httpHeaders.getChildren("property");
330                     if (properties != null && properties.length > 0) {
331                         headers = new HashMap<>();
332                         for (PlexusConfiguration property : properties) {
333                             headers.put(
334                                     property.getChild("name").getValue(),
335                                     property.getChild("value").getValue());
336                         }
337                     }
338                 }
339 
340                 PlexusConfiguration connectTimeoutXml = config.getChild("connectTimeout", false);
341                 if (connectTimeoutXml != null) {
342                     connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
343                 } else {
344                     // fallback configuration name
345                     PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
346                     if (httpConfiguration != null) {
347                         PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
348                         if (httpConfigurationAll != null) {
349                             connectTimeoutXml = httpConfigurationAll.getChild("connectionTimeout", false);
350                             if (connectTimeoutXml != null) {
351                                 connectTimeout = Integer.parseInt(connectTimeoutXml.getValue());
352                                 logger.warn("Settings for server {} uses legacy format", server.getId());
353                             }
354                         }
355                     }
356                 }
357 
358                 PlexusConfiguration requestTimeoutXml = config.getChild("requestTimeout", false);
359                 if (requestTimeoutXml != null) {
360                     requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
361                 } else {
362                     // fallback configuration name
363                     PlexusConfiguration httpConfiguration = config.getChild("httpConfiguration", false);
364                     if (httpConfiguration != null) {
365                         PlexusConfiguration httpConfigurationAll = httpConfiguration.getChild("all", false);
366                         if (httpConfigurationAll != null) {
367                             requestTimeoutXml = httpConfigurationAll.getChild("readTimeout", false);
368                             if (requestTimeoutXml != null) {
369                                 requestTimeout = Integer.parseInt(requestTimeoutXml.getValue());
370                                 logger.warn("Settings for server {} uses legacy format", server.getId());
371                             }
372                         }
373                     }
374                 }
375 
376                 // org.eclipse.aether.ConfigurationProperties.HTTP_HEADERS => Map<String, String>
377                 if (headers != null) {
378                     configProps.put(ConfigurationProperties.HTTP_HEADERS + "." + server.getId(), headers);
379                 }
380                 // org.eclipse.aether.ConfigurationProperties.CONNECT_TIMEOUT => int
381                 if (connectTimeout != null) {
382                     configProps.put(ConfigurationProperties.CONNECT_TIMEOUT + "." + server.getId(), connectTimeout);
383                 }
384                 // org.eclipse.aether.ConfigurationProperties.REQUEST_TIMEOUT => int
385                 if (requestTimeout != null) {
386                     configProps.put(ConfigurationProperties.REQUEST_TIMEOUT + "." + server.getId(), requestTimeout);
387                 }
388             }
389 
390             configProps.put("aether.transport.wagon.perms.fileMode." + server.getId(), server.getFilePermissions());
391             configProps.put("aether.transport.wagon.perms.dirMode." + server.getId(), server.getDirectoryPermissions());
392         }
393         session.setAuthenticationSelector(authSelector);
394 
395         Object transport = configProps.getOrDefault(MAVEN_RESOLVER_TRANSPORT_KEY, MAVEN_RESOLVER_TRANSPORT_DEFAULT);
396         if (MAVEN_RESOLVER_TRANSPORT_DEFAULT.equals(transport)) {
397             // The "default" mode (user did not set anything) from now on defaults to AUTO
398         } else if (MAVEN_RESOLVER_TRANSPORT_JDK.equals(transport)) {
399             // Make sure (whatever extra priority is set) that resolver file/jdk is selected
400             configProps.put(FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
401             configProps.put(JDK_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
402         } else if (MAVEN_RESOLVER_TRANSPORT_APACHE.equals(transport)
403                 || MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
404             if (MAVEN_RESOLVER_TRANSPORT_NATIVE.equals(transport)) {
405                 logger.warn(
406                         "Transport name '{}' is DEPRECATED/RENAMED, use '{}' instead",
407                         MAVEN_RESOLVER_TRANSPORT_NATIVE,
408                         MAVEN_RESOLVER_TRANSPORT_APACHE);
409             }
410             // Make sure (whatever extra priority is set) that resolver file/apache is selected
411             configProps.put(FILE_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
412             configProps.put(APACHE_HTTP_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
413         } else if (MAVEN_RESOLVER_TRANSPORT_WAGON.equals(transport)) {
414             // Make sure (whatever extra priority is set) that wagon is selected
415             configProps.put(WAGON_TRANSPORTER_PRIORITY_KEY, RESOLVER_MAX_PRIORITY);
416         } else if (!MAVEN_RESOLVER_TRANSPORT_AUTO.equals(transport)) {
417             throw new IllegalArgumentException("Unknown resolver transport '" + transport
418                     + "'. Supported transports are: " + MAVEN_RESOLVER_TRANSPORT_WAGON + ", "
419                     + MAVEN_RESOLVER_TRANSPORT_APACHE + ", " + MAVEN_RESOLVER_TRANSPORT_JDK + ", "
420                     + MAVEN_RESOLVER_TRANSPORT_AUTO);
421         }
422 
423         session.setUserProperties(request.getUserProperties());
424         session.setSystemProperties(request.getSystemProperties());
425         session.setConfigProperties(configProps);
426         session.setIgnoreArtifactDescriptorRepositories(request.isIgnoreTransitiveRepositories());
427 
428         session.setTransferListener(request.getTransferListener());
429 
430         RepositoryListener repositoryListener = eventSpyDispatcher.chainListener(new LoggingRepositoryListener(logger));
431 
432         boolean recordReverseTree = configProps.containsKey(MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE)
433                 && Boolean.parseBoolean((String) configProps.get(MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE));
434         if (recordReverseTree) {
435             repositoryListener = new ChainedRepositoryListener(repositoryListener, new ReverseTreeRepositoryListener());
436         }
437         session.setRepositoryListener(repositoryListener);
438 
439         injectMirror(request.getRemoteRepositories(), request.getMirrors());
440         injectProxy(proxySelector, request.getRemoteRepositories());
441         injectAuthentication(authSelector, request.getRemoteRepositories());
442 
443         injectMirror(request.getPluginArtifactRepositories(), request.getMirrors());
444         injectProxy(proxySelector, request.getPluginArtifactRepositories());
445         injectAuthentication(authSelector, request.getPluginArtifactRepositories());
446 
447         String resolverDependencyManagerTransitivity = (String)
448                 configProps.getOrDefault(MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY_KEY, Boolean.TRUE.toString());
449         session.setDependencyManager(
450                 new ClassicDependencyManager(Boolean.parseBoolean(resolverDependencyManagerTransitivity)));
451 
452         ArrayList<File> paths = new ArrayList<>();
453         paths.add(new File(request.getLocalRepository().getBasedir()));
454         String localRepoTail = (String) configProps.get(MAVEN_REPO_LOCAL_TAIL);
455         if (localRepoTail != null) {
456             Arrays.stream(localRepoTail.split(","))
457                     .filter(p -> p != null && !p.trim().isEmpty())
458                     .map(File::new)
459                     .forEach(paths::add);
460         }
461         session.withLocalRepositoryBaseDirectories(paths);
462 
463         return session;
464     }
465 
466     private VersionFilter buildVersionFilter(String filterExpression) {
467         ArrayList<VersionFilter> filters = new ArrayList<>();
468         if (filterExpression != null) {
469             List<String> expressions = Arrays.stream(filterExpression.split(";"))
470                     .filter(s -> s != null && !s.trim().isEmpty())
471                     .collect(Collectors.toList());
472             for (String expression : expressions) {
473                 if ("h".equals(expression)) {
474                     filters.add(new HighestVersionFilter());
475                 } else if (expression.startsWith("h(") && expression.endsWith(")")) {
476                     int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
477                     filters.add(new HighestVersionFilter(num));
478                 } else if ("l".equals(expression)) {
479                     filters.add(new LowestVersionFilter());
480                 } else if (expression.startsWith("l(") && expression.endsWith(")")) {
481                     int num = Integer.parseInt(expression.substring(2, expression.length() - 1));
482                     filters.add(new LowestVersionFilter(num));
483                 } else if ("s".equals(expression)) {
484                     filters.add(new ContextualSnapshotVersionFilter());
485                 } else if (expression.startsWith("e(") && expression.endsWith(")")) {
486                     Artifact artifact = new DefaultArtifact(expression.substring(2, expression.length() - 1));
487                     VersionRange versionRange =
488                             artifact.getVersion().contains(",") ? parseVersionRange(artifact.getVersion()) : null;
489                     Predicate<Artifact> predicate = a -> {
490                         if (artifact.getGroupId().equals(a.getGroupId())
491                                 && artifact.getArtifactId().equals(a.getArtifactId())) {
492                             if (versionRange != null) {
493                                 Version v = parseVersion(a.getVersion());
494                                 return !versionRange.containsVersion(v);
495                             } else {
496                                 return !artifact.getVersion().equals(a.getVersion());
497                             }
498                         }
499                         return true;
500                     };
501                     filters.add(new PredicateVersionFilter(predicate));
502                 } else {
503                     throw new IllegalArgumentException("Unsupported filter expression: " + expression);
504                 }
505             }
506         }
507         if (filters.isEmpty()) {
508             return null;
509         } else if (filters.size() == 1) {
510             return filters.get(0);
511         } else {
512             return ChainedVersionFilter.newInstance(filters);
513         }
514     }
515 
516     private Version parseVersion(String spec) {
517         try {
518             return versionScheme.parseVersion(spec);
519         } catch (InvalidVersionSpecificationException e) {
520             throw new RuntimeException(e);
521         }
522     }
523 
524     private VersionRange parseVersionRange(String spec) {
525         try {
526             return versionScheme.parseVersionRange(spec);
527         } catch (InvalidVersionSpecificationException e) {
528             throw new RuntimeException(e);
529         }
530     }
531 
532     private Map<?, ?> getPropertiesFromRequestedProfiles(MavenExecutionRequest request) {
533         HashSet<String> activeProfileId =
534                 new HashSet<>(request.getProfileActivation().getRequiredActiveProfileIds());
535         activeProfileId.addAll(request.getProfileActivation().getOptionalActiveProfileIds());
536 
537         return request.getProfiles().stream()
538                 .filter(profile -> activeProfileId.contains(profile.getId()))
539                 .map(ModelBase::getProperties)
540                 .flatMap(properties -> properties.entrySet().stream())
541                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k2));
542     }
543 
544     private String getUserAgent() {
545         String version = runtimeInformation.getMavenVersion();
546         version = version.isEmpty() ? version : "/" + version;
547         return "Apache-Maven" + version + " (Java " + System.getProperty("java.version") + "; "
548                 + System.getProperty("os.name") + " " + System.getProperty("os.version") + ")";
549     }
550 
551     private void injectMirror(List<ArtifactRepository> repositories, List<Mirror> mirrors) {
552         if (repositories != null && mirrors != null) {
553             for (ArtifactRepository repository : repositories) {
554                 Mirror mirror = MavenRepositorySystem.getMirror(repository, mirrors);
555                 injectMirror(repository, mirror);
556             }
557         }
558     }
559 
560     private void injectMirror(ArtifactRepository repository, Mirror mirror) {
561         if (mirror != null) {
562             ArtifactRepository original = MavenRepositorySystem.createArtifactRepository(
563                     repository.getId(),
564                     repository.getUrl(),
565                     repository.getLayout(),
566                     repository.getSnapshots(),
567                     repository.getReleases());
568 
569             repository.setMirroredRepositories(Collections.singletonList(original));
570 
571             repository.setId(mirror.getId());
572             repository.setUrl(mirror.getUrl());
573 
574             if (mirror.getLayout() != null && !mirror.getLayout().isEmpty()) {
575                 repository.setLayout(original.getLayout());
576             }
577 
578             repository.setBlocked(mirror.isBlocked());
579         }
580     }
581 
582     private void injectProxy(ProxySelector selector, List<ArtifactRepository> repositories) {
583         if (repositories != null && selector != null) {
584             for (ArtifactRepository repository : repositories) {
585                 repository.setProxy(getProxy(selector, repository));
586             }
587         }
588     }
589 
590     private org.apache.maven.repository.Proxy getProxy(ProxySelector selector, ArtifactRepository repository) {
591         if (selector != null) {
592             RemoteRepository repo = RepositoryUtils.toRepo(repository);
593             org.eclipse.aether.repository.Proxy proxy = selector.getProxy(repo);
594             if (proxy != null) {
595                 org.apache.maven.repository.Proxy p = new org.apache.maven.repository.Proxy();
596                 p.setHost(proxy.getHost());
597                 p.setProtocol(proxy.getType());
598                 p.setPort(proxy.getPort());
599                 if (proxy.getAuthentication() != null) {
600                     repo = new RemoteRepository.Builder(repo).setProxy(proxy).build();
601                     AuthenticationContext authCtx = AuthenticationContext.forProxy(null, repo);
602                     p.setUserName(authCtx.get(AuthenticationContext.USERNAME));
603                     p.setPassword(authCtx.get(AuthenticationContext.PASSWORD));
604                     p.setNtlmDomain(authCtx.get(AuthenticationContext.NTLM_DOMAIN));
605                     p.setNtlmHost(authCtx.get(AuthenticationContext.NTLM_WORKSTATION));
606                     authCtx.close();
607                 }
608                 return p;
609             }
610         }
611         return null;
612     }
613 
614     private void injectAuthentication(AuthenticationSelector selector, List<ArtifactRepository> repositories) {
615         if (repositories != null && selector != null) {
616             for (ArtifactRepository repository : repositories) {
617                 repository.setAuthentication(getAuthentication(selector, repository));
618             }
619         }
620     }
621 
622     private Authentication getAuthentication(AuthenticationSelector selector, ArtifactRepository repository) {
623         if (selector != null) {
624             RemoteRepository repo = RepositoryUtils.toRepo(repository);
625             org.eclipse.aether.repository.Authentication auth = selector.getAuthentication(repo);
626             if (auth != null) {
627                 repo = new RemoteRepository.Builder(repo)
628                         .setAuthentication(auth)
629                         .build();
630                 AuthenticationContext authCtx = AuthenticationContext.forRepository(null, repo);
631                 Authentication result = new Authentication(
632                         authCtx.get(AuthenticationContext.USERNAME), authCtx.get(AuthenticationContext.PASSWORD));
633                 result.setPrivateKey(authCtx.get(AuthenticationContext.PRIVATE_KEY_PATH));
634                 result.setPassphrase(authCtx.get(AuthenticationContext.PRIVATE_KEY_PASSPHRASE));
635                 authCtx.close();
636                 return result;
637             }
638         }
639         return null;
640     }
641 }